summaryrefslogtreecommitdiff
path: root/imap/src/osdep
diff options
context:
space:
mode:
authorEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
committerEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
commit094ca96844842928810f14844413109fc6cdd890 (patch)
treee60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /imap/src/osdep
downloadalpine-094ca96844842928810f14844413109fc6cdd890.tar.xz
Initial Alpine Version
Diffstat (limited to 'imap/src/osdep')
-rw-r--r--imap/src/osdep/amiga/Makefile231
-rw-r--r--imap/src/osdep/amiga/ckp_std.c42
-rwxr-xr-ximap/src/osdep/amiga/drivers36
-rw-r--r--imap/src/osdep/amiga/dummy.c809
-rw-r--r--imap/src/osdep/amiga/dummy.h43
-rw-r--r--imap/src/osdep/amiga/env_ami.c1282
-rw-r--r--imap/src/osdep/amiga/env_ami.h95
-rw-r--r--imap/src/osdep/amiga/fdstring.c99
-rw-r--r--imap/src/osdep/amiga/fdstring.h39
-rw-r--r--imap/src/osdep/amiga/fs_ami.c71
-rw-r--r--imap/src/osdep/amiga/ftl_ami.c38
-rw-r--r--imap/src/osdep/amiga/gethstid.c38
-rw-r--r--imap/src/osdep/amiga/gr_waitp.c39
-rw-r--r--imap/src/osdep/amiga/log_std.c44
-rw-r--r--imap/src/osdep/amiga/mbx.c1855
-rw-r--r--imap/src/osdep/amiga/mh.c1283
-rw-r--r--imap/src/osdep/amiga/mix.c2834
-rwxr-xr-ximap/src/osdep/amiga/mkauths40
-rw-r--r--imap/src/osdep/amiga/mmdf.c2549
-rw-r--r--imap/src/osdep/amiga/mtx.c1371
-rw-r--r--imap/src/osdep/amiga/mx.c1287
-rw-r--r--imap/src/osdep/amiga/news.c738
-rw-r--r--imap/src/osdep/amiga/nl_ami.c92
-rw-r--r--imap/src/osdep/amiga/os_ami.c80
-rw-r--r--imap/src/osdep/amiga/os_ami.h54
-rw-r--r--imap/src/osdep/amiga/phile.c553
-rw-r--r--imap/src/osdep/amiga/pmatch.c89
-rw-r--r--imap/src/osdep/amiga/pseudo.c36
-rw-r--r--imap/src/osdep/amiga/pseudo.h30
-rw-r--r--imap/src/osdep/amiga/scandir.c81
-rw-r--r--imap/src/osdep/amiga/ssl_none.c141
-rw-r--r--imap/src/osdep/amiga/tcp_ami.c797
-rw-r--r--imap/src/osdep/amiga/tcp_ami.h48
-rw-r--r--imap/src/osdep/amiga/tenex.c1470
-rw-r--r--imap/src/osdep/amiga/tz_bsd.c38
-rw-r--r--imap/src/osdep/amiga/unix.c2708
-rw-r--r--imap/src/osdep/amiga/unix.h219
-rw-r--r--imap/src/osdep/amiga/write.c59
-rw-r--r--imap/src/osdep/dos/bezrkdos.c901
-rw-r--r--imap/src/osdep/dos/drivers.bat33
-rw-r--r--imap/src/osdep/dos/drivraux.bat28
-rw-r--r--imap/src/osdep/dos/dummy.h43
-rw-r--r--imap/src/osdep/dos/dummydos.c689
-rw-r--r--imap/src/osdep/dos/env_dos.c300
-rw-r--r--imap/src/osdep/dos/env_dos.h68
-rw-r--r--imap/src/osdep/dos/fdstring.c99
-rw-r--r--imap/src/osdep/dos/fdstring.h39
-rw-r--r--imap/src/osdep/dos/fs_dos.c62
-rw-r--r--imap/src/osdep/dos/ftl_dos.c38
-rw-r--r--imap/src/osdep/dos/makefile98
-rw-r--r--imap/src/osdep/dos/mkautaux.bat29
-rw-r--r--imap/src/osdep/dos/mkauths.bat33
-rw-r--r--imap/src/osdep/dos/mtestdbw.bat27
-rw-r--r--imap/src/osdep/dos/mtestdnf.bat27
-rw-r--r--imap/src/osdep/dos/mtestdnv.bat27
-rw-r--r--imap/src/osdep/dos/mtestdpc.bat27
-rw-r--r--imap/src/osdep/dos/mtestdwa.bat27
-rw-r--r--imap/src/osdep/dos/mtestwsk.bat27
-rw-r--r--imap/src/osdep/dos/mtxdos.c875
-rw-r--r--imap/src/osdep/dos/nl_dos.c61
-rw-r--r--imap/src/osdep/dos/os_dbw.c91
-rw-r--r--imap/src/osdep/dos/os_dbw.h43
-rw-r--r--imap/src/osdep/dos/os_dnf.c95
-rw-r--r--imap/src/osdep/dos/os_dnf.h44
-rw-r--r--imap/src/osdep/dos/os_dnv.c95
-rw-r--r--imap/src/osdep/dos/os_dnv.h43
-rw-r--r--imap/src/osdep/dos/os_dpc.c102
-rw-r--r--imap/src/osdep/dos/os_dpc.h41
-rw-r--r--imap/src/osdep/dos/os_dwa.c72
-rw-r--r--imap/src/osdep/dos/os_dwa.h43
-rw-r--r--imap/src/osdep/dos/os_wsk.c45
-rw-r--r--imap/src/osdep/dos/os_wsk.h48
-rw-r--r--imap/src/osdep/dos/pmatch.c89
-rw-r--r--imap/src/osdep/dos/tcp_dos.c434
-rw-r--r--imap/src/osdep/dos/tcp_dos.h51
-rw-r--r--imap/src/osdep/dos/tcp_dwa.c344
-rw-r--r--imap/src/osdep/dos/tcp_dwa.h45
-rw-r--r--imap/src/osdep/dos/tcp_wsk.c818
-rw-r--r--imap/src/osdep/dos/tcp_wsk.h50
-rw-r--r--imap/src/osdep/dos/write.c59
-rw-r--r--imap/src/osdep/mac/dummy.h43
-rw-r--r--imap/src/osdep/mac/dummymac.c295
-rw-r--r--imap/src/osdep/mac/env_mac.c236
-rw-r--r--imap/src/osdep/mac/env_mac.h58
-rw-r--r--imap/src/osdep/mac/fs_mac.c62
-rw-r--r--imap/src/osdep/mac/ftl_mac.c39
-rw-r--r--imap/src/osdep/mac/linkage.c37
-rw-r--r--imap/src/osdep/mac/linkage.h36
-rw-r--r--imap/src/osdep/mac/mtest.sit.hqx171
-rw-r--r--imap/src/osdep/mac/nl_mac.c74
-rw-r--r--imap/src/osdep/mac/os_mac.c82
-rw-r--r--imap/src/osdep/mac/os_mac.h69
-rw-r--r--imap/src/osdep/mac/osdep.h29
-rw-r--r--imap/src/osdep/mac/pmatch.c89
-rw-r--r--imap/src/osdep/mac/tcp_mac.c557
-rw-r--r--imap/src/osdep/mac/tcp_mac.h49
-rwxr-xr-ximap/src/osdep/nt/drivers.bat33
-rwxr-xr-ximap/src/osdep/nt/drivraux.bat30
-rw-r--r--imap/src/osdep/nt/dummy.h43
-rw-r--r--imap/src/osdep/nt/dummynt.c724
-rw-r--r--imap/src/osdep/nt/env_nt.c774
-rw-r--r--imap/src/osdep/nt/env_nt.h68
-rw-r--r--imap/src/osdep/nt/fdstring.c99
-rw-r--r--imap/src/osdep/nt/fdstring.h39
-rw-r--r--imap/src/osdep/nt/fs_nt.c62
-rw-r--r--imap/src/osdep/nt/ftl_nt.c38
-rw-r--r--imap/src/osdep/nt/ip4_nt.c184
-rw-r--r--imap/src/osdep/nt/ip6_nt.c288
-rw-r--r--imap/src/osdep/nt/kerb_mit.c74
-rw-r--r--imap/src/osdep/nt/kerb_w2k.c699
-rw-r--r--imap/src/osdep/nt/mailfile.h29
-rw-r--r--imap/src/osdep/nt/makefile.nt118
-rw-r--r--imap/src/osdep/nt/makefile.ntk118
-rw-r--r--imap/src/osdep/nt/makefile.w2k119
-rw-r--r--imap/src/osdep/nt/mbxnt.c1694
-rwxr-xr-ximap/src/osdep/nt/mkautaux.bat31
-rwxr-xr-ximap/src/osdep/nt/mkauths.bat33
-rw-r--r--imap/src/osdep/nt/mtxnt.c1232
-rw-r--r--imap/src/osdep/nt/nl_nt.c61
-rw-r--r--imap/src/osdep/nt/os_nt.c48
-rw-r--r--imap/src/osdep/nt/os_nt.h60
-rw-r--r--imap/src/osdep/nt/os_ntk.c51
-rw-r--r--imap/src/osdep/nt/os_old.c48
-rw-r--r--imap/src/osdep/nt/os_w2k.c49
-rw-r--r--imap/src/osdep/nt/pmatch.c89
-rw-r--r--imap/src/osdep/nt/pseudo.c36
-rw-r--r--imap/src/osdep/nt/pseudo.h30
-rwxr-xr-ximap/src/osdep/nt/setproto.bat29
-rw-r--r--imap/src/osdep/nt/ssl_none.c141
-rw-r--r--imap/src/osdep/nt/ssl_nt.c721
-rw-r--r--imap/src/osdep/nt/ssl_old.c625
-rw-r--r--imap/src/osdep/nt/ssl_w2k.c683
-rw-r--r--imap/src/osdep/nt/tcp_nt.c916
-rw-r--r--imap/src/osdep/nt/tcp_nt.h51
-rw-r--r--imap/src/osdep/nt/tenexnt.c1310
-rw-r--r--imap/src/osdep/nt/unixnt.c2297
-rw-r--r--imap/src/osdep/nt/unixnt.h161
-rw-r--r--imap/src/osdep/nt/write.c59
-rw-r--r--imap/src/osdep/nt/yunchan.c286
-rw-r--r--imap/src/osdep/nt/yunchan.h86
-rw-r--r--imap/src/osdep/os2/auths.cmd48
-rw-r--r--imap/src/osdep/os2/drivers.cmd45
-rw-r--r--imap/src/osdep/os2/dummy.h43
-rw-r--r--imap/src/osdep/os2/dummyos2.c714
-rw-r--r--imap/src/osdep/os2/env_os2.c318
-rw-r--r--imap/src/osdep/os2/env_os2.h62
-rw-r--r--imap/src/osdep/os2/fs_os2.c62
-rw-r--r--imap/src/osdep/os2/ftl_os2.c38
-rw-r--r--imap/src/osdep/os2/makefile.os2109
-rw-r--r--imap/src/osdep/os2/mbxnt.c1694
-rw-r--r--imap/src/osdep/os2/mtxnt.c1232
-rw-r--r--imap/src/osdep/os2/nl_os2.c61
-rw-r--r--imap/src/osdep/os2/os_os2.c100
-rw-r--r--imap/src/osdep/os2/os_os2.h38
-rw-r--r--imap/src/osdep/os2/pmatch.c89
-rw-r--r--imap/src/osdep/os2/pseudo.c36
-rw-r--r--imap/src/osdep/os2/pseudo.h30
-rw-r--r--imap/src/osdep/os2/setproto.cmd34
-rw-r--r--imap/src/osdep/os2/tcp_os2.c434
-rw-r--r--imap/src/osdep/os2/tcp_os2.h51
-rw-r--r--imap/src/osdep/os2/tenexnt.c1310
-rw-r--r--imap/src/osdep/os2/unixnt.c2297
-rw-r--r--imap/src/osdep/os2/unixnt.h161
-rw-r--r--imap/src/osdep/os2/write.c59
-rw-r--r--imap/src/osdep/tops-20/build.ctl63
-rw-r--r--imap/src/osdep/tops-20/dummy.h43
-rw-r--r--imap/src/osdep/tops-20/dummyt20.c294
-rw-r--r--imap/src/osdep/tops-20/env_t20.c226
-rw-r--r--imap/src/osdep/tops-20/env_t20.h73
-rw-r--r--imap/src/osdep/tops-20/fs_t20.c62
-rw-r--r--imap/src/osdep/tops-20/ftl_t20.c38
-rw-r--r--imap/src/osdep/tops-20/linkage.c37
-rw-r--r--imap/src/osdep/tops-20/linkage.h36
-rw-r--r--imap/src/osdep/tops-20/log_t20.c80
-rw-r--r--imap/src/osdep/tops-20/nl_t20.c61
-rw-r--r--imap/src/osdep/tops-20/os_t20.c106
-rw-r--r--imap/src/osdep/tops-20/os_t20.h52
-rw-r--r--imap/src/osdep/tops-20/pmatch.c89
-rw-r--r--imap/src/osdep/tops-20/shortsym.h608
-rw-r--r--imap/src/osdep/tops-20/tcp_t20.c365
-rw-r--r--imap/src/osdep/tops-20/tcp_t20.h65
-rw-r--r--imap/src/osdep/unix/Makefile1078
-rw-r--r--imap/src/osdep/unix/Makefile.gss39
-rw-r--r--imap/src/osdep/unix/ckp_1st.c53
-rw-r--r--imap/src/osdep/unix/ckp_2nd.c36
-rw-r--r--imap/src/osdep/unix/ckp_3rd.c32
-rw-r--r--imap/src/osdep/unix/ckp_a41.c52
-rw-r--r--imap/src/osdep/unix/ckp_afs.c71
-rw-r--r--imap/src/osdep/unix/ckp_bsi.c50
-rw-r--r--imap/src/osdep/unix/ckp_cyg.c64
-rw-r--r--imap/src/osdep/unix/ckp_dce.c83
-rw-r--r--imap/src/osdep/unix/ckp_gss.c90
-rw-r--r--imap/src/osdep/unix/ckp_nul.c40
-rw-r--r--imap/src/osdep/unix/ckp_os4.c78
-rw-r--r--imap/src/osdep/unix/ckp_pam.c136
-rw-r--r--imap/src/osdep/unix/ckp_pmb.c128
-rw-r--r--imap/src/osdep/unix/ckp_psx.c101
-rw-r--r--imap/src/osdep/unix/ckp_sce.c51
-rw-r--r--imap/src/osdep/unix/ckp_sec.c50
-rw-r--r--imap/src/osdep/unix/ckp_ssn.c56
-rw-r--r--imap/src/osdep/unix/ckp_std.c42
-rw-r--r--imap/src/osdep/unix/ckp_sv4.c90
-rw-r--r--imap/src/osdep/unix/ckp_svo.c89
-rw-r--r--imap/src/osdep/unix/ckp_ult.c40
-rw-r--r--imap/src/osdep/unix/crx_nfs.c79
-rw-r--r--imap/src/osdep/unix/crx_std.c45
-rwxr-xr-ximap/src/osdep/unix/drivers36
-rw-r--r--imap/src/osdep/unix/dummy.c809
-rw-r--r--imap/src/osdep/unix/dummy.h43
-rw-r--r--imap/src/osdep/unix/env_unix.c1847
-rw-r--r--imap/src/osdep/unix/env_unix.h95
-rw-r--r--imap/src/osdep/unix/fdstring.c99
-rw-r--r--imap/src/osdep/unix/fdstring.h39
-rw-r--r--imap/src/osdep/unix/flockcyg.c92
-rw-r--r--imap/src/osdep/unix/flockcyg.h50
-rw-r--r--imap/src/osdep/unix/flocklnx.c76
-rw-r--r--imap/src/osdep/unix/flocksim.c920
-rw-r--r--imap/src/osdep/unix/flocksim.h117
-rw-r--r--imap/src/osdep/unix/fs_unix.c71
-rw-r--r--imap/src/osdep/unix/fsync.c39
-rw-r--r--imap/src/osdep/unix/ftl_unix.c39
-rw-r--r--imap/src/osdep/unix/gethstid.c38
-rw-r--r--imap/src/osdep/unix/getspnam.c65
-rw-r--r--imap/src/osdep/unix/gr_wait.c46
-rw-r--r--imap/src/osdep/unix/gr_wait4.c39
-rw-r--r--imap/src/osdep/unix/gr_waitp.c39
-rw-r--r--imap/src/osdep/unix/ip4_unix.c184
-rw-r--r--imap/src/osdep/unix/ip6_unix.c288
-rw-r--r--imap/src/osdep/unix/ipo_unix.c181
-rw-r--r--imap/src/osdep/unix/kerb_mit.c111
-rw-r--r--imap/src/osdep/unix/log_bsi.c55
-rw-r--r--imap/src/osdep/unix/log_cyg.c46
-rw-r--r--imap/src/osdep/unix/log_old.c39
-rw-r--r--imap/src/osdep/unix/log_os4.c58
-rw-r--r--imap/src/osdep/unix/log_sec.c44
-rw-r--r--imap/src/osdep/unix/log_std.c44
-rw-r--r--imap/src/osdep/unix/log_sv4.c43
-rw-r--r--imap/src/osdep/unix/mbx.c1855
-rw-r--r--imap/src/osdep/unix/mh.c1283
-rw-r--r--imap/src/osdep/unix/mix.c2834
-rwxr-xr-ximap/src/osdep/unix/mkauths40
-rw-r--r--imap/src/osdep/unix/mmdf.c2549
-rw-r--r--imap/src/osdep/unix/mtx.c1371
-rw-r--r--imap/src/osdep/unix/mx.c1287
-rw-r--r--imap/src/osdep/unix/news.c738
-rw-r--r--imap/src/osdep/unix/nl_unix.c92
-rw-r--r--imap/src/osdep/unix/opendir.c79
-rw-r--r--imap/src/osdep/unix/os_a32.c60
-rw-r--r--imap/src/osdep/unix/os_a32.h50
-rw-r--r--imap/src/osdep/unix/os_a41.c61
-rw-r--r--imap/src/osdep/unix/os_a41.h50
-rw-r--r--imap/src/osdep/unix/os_a52.c63
-rw-r--r--imap/src/osdep/unix/os_a52.h53
-rw-r--r--imap/src/osdep/unix/os_aix.c64
-rw-r--r--imap/src/osdep/unix/os_aix.h47
-rw-r--r--imap/src/osdep/unix/os_aos.c63
-rw-r--r--imap/src/osdep/unix/os_aos.h50
-rw-r--r--imap/src/osdep/unix/os_art.c86
-rw-r--r--imap/src/osdep/unix/os_art.h81
-rw-r--r--imap/src/osdep/unix/os_asv.c70
-rw-r--r--imap/src/osdep/unix/os_asv.h76
-rw-r--r--imap/src/osdep/unix/os_aux.c62
-rw-r--r--imap/src/osdep/unix/os_aux.h51
-rw-r--r--imap/src/osdep/unix/os_bsd.c63
-rw-r--r--imap/src/osdep/unix/os_bsd.h51
-rw-r--r--imap/src/osdep/unix/os_bsf.c54
-rw-r--r--imap/src/osdep/unix/os_bsf.h46
-rw-r--r--imap/src/osdep/unix/os_bsi.c54
-rw-r--r--imap/src/osdep/unix/os_bsi.h43
-rw-r--r--imap/src/osdep/unix/os_cvx.c57
-rw-r--r--imap/src/osdep/unix/os_cvx.h45
-rw-r--r--imap/src/osdep/unix/os_cyg.c71
-rw-r--r--imap/src/osdep/unix/os_cyg.h71
-rw-r--r--imap/src/osdep/unix/os_d-g.c54
-rw-r--r--imap/src/osdep/unix/os_d-g.h56
-rw-r--r--imap/src/osdep/unix/os_do4.c58
-rw-r--r--imap/src/osdep/unix/os_do4.h47
-rw-r--r--imap/src/osdep/unix/os_drs.c57
-rw-r--r--imap/src/osdep/unix/os_drs.h59
-rw-r--r--imap/src/osdep/unix/os_dyn.c64
-rw-r--r--imap/src/osdep/unix/os_dyn.h61
-rw-r--r--imap/src/osdep/unix/os_hpp.c77
-rw-r--r--imap/src/osdep/unix/os_hpp.h63
-rw-r--r--imap/src/osdep/unix/os_isc.c68
-rw-r--r--imap/src/osdep/unix/os_isc.h70
-rw-r--r--imap/src/osdep/unix/os_lnx.c54
-rw-r--r--imap/src/osdep/unix/os_lnx.h67
-rw-r--r--imap/src/osdep/unix/os_lyn.c54
-rw-r--r--imap/src/osdep/unix/os_lyn.h44
-rw-r--r--imap/src/osdep/unix/os_mct.c52
-rw-r--r--imap/src/osdep/unix/os_mct.h44
-rw-r--r--imap/src/osdep/unix/os_mnt.c53
-rw-r--r--imap/src/osdep/unix/os_mnt.h51
-rw-r--r--imap/src/osdep/unix/os_nto.c76
-rw-r--r--imap/src/osdep/unix/os_nto.h75
-rw-r--r--imap/src/osdep/unix/os_nxt.c54
-rw-r--r--imap/src/osdep/unix/os_nxt.h50
-rw-r--r--imap/src/osdep/unix/os_os4.c57
-rw-r--r--imap/src/osdep/unix/os_os4.h51
-rw-r--r--imap/src/osdep/unix/os_osf.c55
-rw-r--r--imap/src/osdep/unix/os_osf.h51
-rw-r--r--imap/src/osdep/unix/os_osx.c54
-rw-r--r--imap/src/osdep/unix/os_osx.h56
-rw-r--r--imap/src/osdep/unix/os_ptx.c115
-rw-r--r--imap/src/osdep/unix/os_ptx.h72
-rw-r--r--imap/src/osdep/unix/os_pyr.c61
-rw-r--r--imap/src/osdep/unix/os_pyr.h58
-rw-r--r--imap/src/osdep/unix/os_qnx.c77
-rw-r--r--imap/src/osdep/unix/os_qnx.h62
-rw-r--r--imap/src/osdep/unix/os_s40.c64
-rw-r--r--imap/src/osdep/unix/os_s40.h33
-rw-r--r--imap/src/osdep/unix/os_sc5.c63
-rw-r--r--imap/src/osdep/unix/os_sc5.h76
-rw-r--r--imap/src/osdep/unix/os_sco.c66
-rw-r--r--imap/src/osdep/unix/os_sco.h79
-rw-r--r--imap/src/osdep/unix/os_sgi.c57
-rw-r--r--imap/src/osdep/unix/os_sgi.h59
-rw-r--r--imap/src/osdep/unix/os_shp.c79
-rw-r--r--imap/src/osdep/unix/os_shp.h63
-rw-r--r--imap/src/osdep/unix/os_slx.c56
-rw-r--r--imap/src/osdep/unix/os_slx.h67
-rw-r--r--imap/src/osdep/unix/os_sol.c71
-rw-r--r--imap/src/osdep/unix/os_soln.h87
-rw-r--r--imap/src/osdep/unix/os_solo.h84
-rw-r--r--imap/src/osdep/unix/os_sos.c57
-rw-r--r--imap/src/osdep/unix/os_sos.h51
-rw-r--r--imap/src/osdep/unix/os_sua.c54
-rw-r--r--imap/src/osdep/unix/os_sua.h50
-rw-r--r--imap/src/osdep/unix/os_sun.c64
-rw-r--r--imap/src/osdep/unix/os_sun.h51
-rw-r--r--imap/src/osdep/unix/os_sv2.c128
-rw-r--r--imap/src/osdep/unix/os_sv2.h120
-rw-r--r--imap/src/osdep/unix/os_sv4.c68
-rw-r--r--imap/src/osdep/unix/os_sv4.h78
-rw-r--r--imap/src/osdep/unix/os_ult.c52
-rw-r--r--imap/src/osdep/unix/os_ult.h42
-rw-r--r--imap/src/osdep/unix/os_vu2.c82
-rw-r--r--imap/src/osdep/unix/os_vu2.h79
-rw-r--r--imap/src/osdep/unix/phile.c553
-rw-r--r--imap/src/osdep/unix/pmatch.c89
-rw-r--r--imap/src/osdep/unix/pseudo.c36
-rw-r--r--imap/src/osdep/unix/pseudo.h30
-rw-r--r--imap/src/osdep/unix/rename.c44
-rw-r--r--imap/src/osdep/unix/scandir.c81
-rw-r--r--imap/src/osdep/unix/setpgrp.c39
-rw-r--r--imap/src/osdep/unix/sig_bsd.c40
-rw-r--r--imap/src/osdep/unix/sig_psx.c51
-rw-r--r--imap/src/osdep/unix/sig_sv4.c40
-rw-r--r--imap/src/osdep/unix/ssl_none.c141
-rw-r--r--imap/src/osdep/unix/ssl_unix.c821
-rw-r--r--imap/src/osdep/unix/sslstdio.c169
-rw-r--r--imap/src/osdep/unix/strerror.c37
-rw-r--r--imap/src/osdep/unix/tcp_unix.c1043
-rw-r--r--imap/src/osdep/unix/tcp_unix.h48
-rw-r--r--imap/src/osdep/unix/tenex.c1470
-rw-r--r--imap/src/osdep/unix/truncate.c43
-rw-r--r--imap/src/osdep/unix/tz_bsd.c38
-rw-r--r--imap/src/osdep/unix/tz_nul.c36
-rw-r--r--imap/src/osdep/unix/tz_sv4.c39
-rw-r--r--imap/src/osdep/unix/unix.c2708
-rw-r--r--imap/src/osdep/unix/unix.h161
-rw-r--r--imap/src/osdep/unix/utime.c45
-rw-r--r--imap/src/osdep/unix/write.c59
-rw-r--r--imap/src/osdep/vms/build.com99
-rw-r--r--imap/src/osdep/vms/clean.com26
-rw-r--r--imap/src/osdep/vms/dummy.h43
-rw-r--r--imap/src/osdep/vms/dummyvms.c295
-rw-r--r--imap/src/osdep/vms/env_vms.c174
-rw-r--r--imap/src/osdep/vms/env_vms.h60
-rw-r--r--imap/src/osdep/vms/fs_vms.c62
-rw-r--r--imap/src/osdep/vms/ftl_vms.c38
-rw-r--r--imap/src/osdep/vms/link.opt1
-rw-r--r--imap/src/osdep/vms/link_mnt.opt1
-rw-r--r--imap/src/osdep/vms/link_nlb.opt1
-rw-r--r--imap/src/osdep/vms/linkage.c37
-rw-r--r--imap/src/osdep/vms/linkage.h36
-rw-r--r--imap/src/osdep/vms/nl_vms.c92
-rw-r--r--imap/src/osdep/vms/os_vms.c76
-rw-r--r--imap/src/osdep/vms/os_vms.h52
-rw-r--r--imap/src/osdep/vms/pmatch.c89
-rw-r--r--imap/src/osdep/vms/tcp_vms.h52
-rw-r--r--imap/src/osdep/vms/tcp_vmsl.c378
-rw-r--r--imap/src/osdep/vms/tcp_vmsm.c479
-rw-r--r--imap/src/osdep/vms/tcp_vmsn.c222
-rw-r--r--imap/src/osdep/wce/drivers.bat33
-rw-r--r--imap/src/osdep/wce/drivraux.bat30
-rw-r--r--imap/src/osdep/wce/dummy.h43
-rw-r--r--imap/src/osdep/wce/dummywce.c301
-rw-r--r--imap/src/osdep/wce/env_wce.c301
-rw-r--r--imap/src/osdep/wce/env_wce.h70
-rw-r--r--imap/src/osdep/wce/fs_wce.c62
-rw-r--r--imap/src/osdep/wce/ftl_wce.c38
-rw-r--r--imap/src/osdep/wce/makefile.wce96
-rw-r--r--imap/src/osdep/wce/mkautaux.bat31
-rw-r--r--imap/src/osdep/wce/mkauths.bat33
-rw-r--r--imap/src/osdep/wce/nl_wce.c61
-rw-r--r--imap/src/osdep/wce/os_wce.c45
-rw-r--r--imap/src/osdep/wce/os_wce.h53
-rw-r--r--imap/src/osdep/wce/pmatch.c89
-rw-r--r--imap/src/osdep/wce/setproto.bat29
-rw-r--r--imap/src/osdep/wce/tcp_wce.c818
-rw-r--r--imap/src/osdep/wce/tcp_wce.h46
402 files changed, 93768 insertions, 0 deletions
diff --git a/imap/src/osdep/amiga/Makefile b/imap/src/osdep/amiga/Makefile
new file mode 100644
index 00000000..1f08e97e
--- /dev/null
+++ b/imap/src/osdep/amiga/Makefile
@@ -0,0 +1,231 @@
+# ========================================================================
+# Copyright 1988-2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# ========================================================================
+
+
+# Program: C client makefile for Amiga
+#
+# Author: Mark Crispin
+# Networks and Distributed Computing
+# Computing & Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, WA 98195
+# Internet: MRC@CAC.Washington.EDU
+#
+# Date: 11 May 1989
+# Last Edited: 5 November 2006
+
+
+# Command line build parameters
+
+EXTRAAUTHENTICATORS=
+EXTRADRIVERS=mbox
+PASSWDTYPE=std
+
+
+# Build parameters normally set by the individual port
+
+AMICFLAGS=-O -DNO_INLINE_STDARG -Dunix
+AMILDFLAGS=/pine/libc.a -lamiga -lauto
+CHECKPW=std
+LOGINPW=std
+ACTIVEFILE=/UULib/News/Active
+SPOOLDIR=/usr/spool
+MAILSPOOL=/AmiTCP/Mail
+NEWSSPOOL=/UUNews
+MD5PWD="/etc/cram-md5.pwd"
+
+
+# Default formats for creating new mailboxes and for empty mailboxes in the
+# default namespace; must be set to the associated driver's prototype.
+#
+# The CREATEPROTO is the default format for new mailbox creation.
+# The EMPTYPROTO is the default format for handling zero-byte files.
+#
+# Normally, this is set by the individual port.
+#
+# NOTE: namespace formats (e.g. mh and news) can not be set as a default format
+# since they do not exist in the default namespace. Also, it is meaningless to
+# set certain other formats (e.g. mbx, mx, and mix) as the EMPTYPROTO since
+# these formats can never be empty files.
+
+CREATEPROTO=unixproto
+EMPTYPROTO=unixproto
+
+
+# Commands possibly overriden by the individual port
+
+ARRC=ar rc
+CC=cc
+LN=cp
+RANLIB=ranlib
+RM=rm -f
+
+
+# Standard distribution build parameters
+
+DEFAULTAUTHENTICATORS=ext md5 pla log
+DEFAULTDRIVERS=imap nntp pop3 mix mx mbx tenex mtx mh mmdf unix news phile
+CHUNKSIZE=65536
+
+
+# Normally no need to change any of these
+
+ARCHIVE=c-client.a
+BINARIES=mail.o misc.o newsrc.o smanager.o osdep.o utf8.o utf8aux.o \
+ dummy.o pseudo.o netmsg.o flstring.o fdstring.o \
+ rfc822.o nntp.o smtp.o imap4r1.o pop3.o \
+ unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o mix.o
+CFLAGS=$(BASECFLAGS) $(EXTRACFLAGS)
+MAKE=make
+MV=mv
+SHELL=/bin/sh
+
+
+# Primary build command
+
+BUILDOPTIONS= EXTRACFLAGS=$(EXTRACFLAGS) EXTRALDFLAGS=$(EXTRALDFLAGS)\
+ EXTRADRIVERS=$(EXTRADRIVERS) EXTRAAUTHENTICATORS=$(EXTRAAUTHENTICATORS)\
+ PASSWDTYPE=$(PASSWDTYPE)
+BUILD=$(MAKE) build $(BUILDOPTIONS) $(SPECIALS)
+
+
+# Here if no make argument established
+
+missing: osdep.h
+ $(MAKE) $(ARCHIVE) CC=`cat CCTYPE` CFLAGS="`cat CFLAGS`"
+
+osdep.h:
+ @echo You must specify what type of system
+ @false
+
+
+# Current ports
+
+ami: # AmigaDOS
+ $(BUILD) OS=$@ \
+ BASECFLAGS="-DOLD $(AMICFLAGS)" \
+ BASELDFLAGS="$(AMILDFLAGS) -lamitcp000" \
+ CC=gcc
+
+am2: # AmigaDOS with a 68020+
+ $(BUILD) OS=ami \
+ BASECFLAGS="-DOLD -m68020 $(AMICFLAGS)" \
+ BASELDFLAGS="$(AMILDFLAGS) -lamitcp" \
+ CC=gcc
+
+amn: # AmigaDOS with a 680x0 using "new" socket library
+ $(BUILD) OS=ami \
+ BASELDFLAGS="$(AMILDFLAGS) -lnewamitcp000" \
+ CC=gcc
+
+ama: # AmigaDOS using AS225R2
+ $(BUILD) OS=ami \
+ MAILSPOOL=/INet/Mail \
+ BASECFLAGS="-m68020 $(AMICFLAGS)" \
+ BASELDFLAGS="$(AMILDFLAGS) -las225r2" \
+ CC=gcc
+
+# Build it!
+
+build: clean once ckp$(PASSWDTYPE) $(EXTRAAUTHENTICATORS) $(ARCHIVE)
+
+$(ARCHIVE): $(BINARIES)
+ $(RM) $(ARCHIVE) || true
+ $(ARRC) $(ARCHIVE) $(BINARIES)
+ $(RANLIB) $(ARCHIVE)
+
+# Cleanup
+
+clean:
+ $(RM) *.o linkage.[ch] auths.c $(ARCHIVE) osdep.* *TYPE *FLAGS || true
+
+
+# Dependencies
+
+dummy.o: mail.h misc.h osdep.h dummy.h
+fdstring.o: mail.h misc.h osdep.h fdstring.h
+flstring.o: mail.h misc.h osdep.h flstring.h
+imap4r1.o: mail.h misc.h osdep.h imap4r1.h rfc822.h
+mail.o: mail.h misc.h osdep.h rfc822.h linkage.h
+mbx.o: mail.h misc.h osdep.h dummy.h
+mh.o: mail.h misc.h osdep.h mh.h dummy.h
+mix.o: mail.h misc.h osdep.h dummy.h
+mx.o: mail.h misc.h osdep.h mx.h dummy.h
+misc.o: mail.h misc.h osdep.h
+mmdf.o: mail.h misc.h osdep.h pseudo.h dummy.h
+mtx.o: mail.h misc.h osdep.h dummy.h
+netmsg.o: mail.h misc.h osdep.h netmsg.h
+news.o: mail.h misc.h osdep.h
+newsrc.o: mail.h misc.h osdep.h newsrc.h
+nntp.o: mail.h misc.h osdep.h netmsg.h smtp.h nntp.h rfc822.h
+phile.o: mail.h misc.h osdep.h rfc822.h dummy.h
+pseudo.o: pseudo.h
+pop3.o: mail.h misc.h osdep.h pop3.h rfc822.h
+smanager.o: mail.h misc.h osdep.h
+smtp.o: mail.h misc.h osdep.h smtp.h rfc822.h
+rfc822.o: mail.h misc.h osdep.h rfc822.h
+tenex.o: mail.h misc.h osdep.h dummy.h
+unix.o: mail.h misc.h osdep.h unix.h pseudo.h dummy.h
+utf8.o: mail.h misc.h osdep.h utf8.h
+utf8aux.o: mail.h misc.h osdep.h utf8.h
+
+
+# OS-dependent
+
+osdep.o:mail.h misc.h env.h fs.h ftl.h nl.h tcp.h \
+ osdep.h env_ami.h tcp_ami.h \
+ osdep.c env_ami.c fs_ami.c ftl_ami.c nl_ami.c tcp_ami.c \
+ auths.c gethstid.c \
+ gr_waitp.c \
+ auth_log.c auth_md5.c auth_pla.c \
+ pmatch.c scandir.c \
+ tz_bsd.c \
+ write.c \
+ strerror.c strpbrk.c strstr.c strtok.c strtoul.c \
+ OSCFLAGS
+ $(CC) $(CFLAGS) `cat OSCFLAGS` -c osdep.c
+
+osdep.c: osdepbas.c osdepckp.c osdeplog.c osdepssl.c
+ $(RM) osdep.c || true
+ cat osdepbas.c osdepckp.c osdeplog.c osdepssl.c > osdep.c
+
+
+# Once-only environment setup
+
+once:
+ @echo Once-only environment setup...
+ ./drivers $(EXTRADRIVERS) $(DEFAULTDRIVERS) dummy
+ ./mkauths $(EXTRAAUTHENTICATORS) $(DEFAULTAUTHENTICATORS)
+ echo $(CC) > CCTYPE
+ echo $(CFLAGS) -DCHUNKSIZE=$(CHUNKSIZE) > CFLAGS
+ echo -DCREATEPROTO=$(CREATEPROTO) -DEMPTYPROTO=$(EMPTYPROTO) \
+ -DMD5ENABLE=\"$(MD5PWD)\" -DMAILSPOOL=\"$(MAILSPOOL)\" \
+ -DACTIVEFILE=\"$(ACTIVEFILE)\" -DNEWSSPOOL=\"$(NEWSSPOOL)\" \
+ -DANONYMOUSHOME=\"$(MAILSPOOL)/anonymous\" > OSCFLAGS
+ echo $(BASELDFLAGS) $(EXTRALDFLAGS) > LDFLAGS
+ $(LN) os_$(OS).h osdep.h
+ $(LN) os_$(OS).c osdepbas.c
+ $(LN) log_$(LOGINPW).c osdeplog.c
+ $(LN) ssl_none.c osdepssl.c
+
+
+# Password checkers
+
+ckpstd: # Port standard
+ $(LN) ckp_$(CHECKPW).c osdepckp.c
+
+
+# A monument to a hack of long ago and far away...
+
+love:
+ @echo not war?
diff --git a/imap/src/osdep/amiga/ckp_std.c b/imap/src/osdep/amiga/ckp_std.c
new file mode 100644
index 00000000..36255cc1
--- /dev/null
+++ b/imap/src/osdep/amiga/ckp_std.c
@@ -0,0 +1,42 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Standard check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ return (pw->pw_passwd && pw->pw_passwd[0] && pw->pw_passwd[1] &&
+ !strcmp (pw->pw_passwd,(char *) crypt (pass,pw->pw_passwd))) ?
+ pw : NIL;
+}
diff --git a/imap/src/osdep/amiga/drivers b/imap/src/osdep/amiga/drivers
new file mode 100755
index 00000000..bd49365f
--- /dev/null
+++ b/imap/src/osdep/amiga/drivers
@@ -0,0 +1,36 @@
+#!/bin/sh
+# ========================================================================
+# Copyright 1988-2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# ========================================================================
+
+# Program: Driver Linkage Generator
+#
+# Author: Mark Crispin
+# Networks and Distributed Computing
+# Computing & Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, WA 98195
+# Internet: MRC@CAC.Washington.EDU
+#
+# Date: 11 October 1989
+# Last Edited: 30 August 2006
+
+
+# Erase old driver linkage
+rm -f linkage.[ch]
+
+# Now define the new list
+for driver
+ do
+ echo "extern DRIVER "$driver"driver;" >> linkage.h
+ echo " mail_link (&"$driver"driver); /* link in the $driver driver */" | cat >> linkage.c
+done
diff --git a/imap/src/osdep/amiga/dummy.c b/imap/src/osdep/amiga/dummy.c
new file mode 100644
index 00000000..b003a0ba
--- /dev/null
+++ b/imap/src/osdep/amiga/dummy.c
@@ -0,0 +1,809 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 9 May 1991
+ * Last Edited: 1 June 2007
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include "dummy.h"
+#include "misc.h"
+
+/* Function prototypes */
+
+DRIVER *dummy_valid (char *name);
+void *dummy_parameters (long function,void *value);
+void dummy_list_work (MAILSTREAM *stream,char *dir,char *pat,char *contents,
+ long level);
+long dummy_listed (MAILSTREAM *stream,char delimiter,char *name,
+ long attributes,char *contents);
+long dummy_subscribe (MAILSTREAM *stream,char *mailbox);
+MAILSTREAM *dummy_open (MAILSTREAM *stream);
+void dummy_close (MAILSTREAM *stream,long options);
+long dummy_ping (MAILSTREAM *stream);
+void dummy_check (MAILSTREAM *stream);
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options);
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+/* Dummy routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER dummydriver = {
+ "dummy", /* driver name */
+ DR_LOCAL|DR_MAIL, /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ dummy_valid, /* mailbox is valid for us */
+ dummy_parameters, /* manipulate parameters */
+ dummy_scan, /* scan mailboxes */
+ dummy_list, /* list mailboxes */
+ dummy_lsub, /* list subscribed mailboxes */
+ dummy_subscribe, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ dummy_delete, /* delete mailbox */
+ dummy_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ dummy_open, /* open mailbox */
+ dummy_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message structure */
+ NIL, /* fetch header */
+ NIL, /* fetch text */
+ NIL, /* fetch message data */
+ NIL, /* unique identifier */
+ NIL, /* message number from UID */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ dummy_ping, /* ping mailbox to see if still alive */
+ dummy_check, /* check for new messages */
+ dummy_expunge, /* expunge deleted messages */
+ dummy_copy, /* copy messages to another mailbox */
+ dummy_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM dummyproto = {&dummydriver};
+
+/* Dummy validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *dummy_valid (char *name)
+{
+ char *s,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* must be valid local mailbox */
+ if (name && *name && (*name != '{') && (s = mailboxfile (tmp,name))) {
+ /* indeterminate clearbox INBOX */
+ if (!*s) return &dummydriver;
+ else if (!stat (s,&sbuf)) switch (sbuf.st_mode & S_IFMT) {
+ case S_IFREG:
+ case S_IFDIR:
+ return &dummydriver;
+ }
+ /* blackbox INBOX does not exist yet */
+ else if (!compare_cstring (name,"INBOX")) return &dummydriver;
+ }
+ return NIL;
+}
+
+
+/* Dummy manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *dummy_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = dummy_file ((char *) value,"INBOX");
+ break;
+ }
+ return ret;
+}
+
+/* Dummy scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ DRIVER *drivers;
+ char *s,test[MAILTMPLEN],file[MAILTMPLEN];
+ long i;
+ if (!pat || !*pat) { /* empty pattern? */
+ if (dummy_canonicalize (test,ref,"*")) {
+ /* tie off name at root */
+ if (s = strchr (test,'/')) *++s = '\0';
+ else test[0] = '\0';
+ dummy_listed (stream,'/',test,LATT_NOSELECT,NIL);
+ }
+ }
+ /* get canonical form of name */
+ else if (dummy_canonicalize (test,ref,pat)) {
+ /* found any wildcards? */
+ if (s = strpbrk (test,"%*")) {
+ /* yes, copy name up to that point */
+ strncpy (file,test,i = s - test);
+ file[i] = '\0'; /* tie off */
+ }
+ else strcpy (file,test); /* use just that name then */
+ if (s = strrchr (file,'/')){/* find directory name */
+ *++s = '\0'; /* found, tie off at that point */
+ s = file;
+ }
+ /* silly case */
+ else if ((file[0] == '~') || (file[0] == '#')) s = file;
+ /* do the work */
+ dummy_list_work (stream,s,test,contents,0);
+ /* always an INBOX */
+ if (pmatch ("INBOX",ucase (test))) {
+ /* done if have a dirfmt INBOX */
+ for (drivers = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL);
+ drivers && !(!(drivers->flags & DR_DISABLE) &&
+ (drivers->flags & DR_DIRFMT) &&
+ (*drivers->valid) ("INBOX")); drivers = drivers->next);
+ /* list INBOX appropriately */
+ dummy_listed (stream,drivers ? '/' : NIL,"INBOX",
+ drivers ? NIL : LATT_NOINFERIORS,contents);
+ }
+ }
+}
+
+
+/* Dummy list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ dummy_scan (stream,ref,pat,NIL);
+}
+
+/* Dummy list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ void *sdb = NIL;
+ char *s,*t,test[MAILTMPLEN],tmp[MAILTMPLEN];
+ int showuppers = pat[strlen (pat) - 1] == '%';
+ /* get canonical form of name */
+ if (dummy_canonicalize (test,ref,pat) && (s = sm_read (&sdb))) do
+ if (*s != '{') {
+ if (!compare_cstring (s,"INBOX") &&
+ pmatch ("INBOX",ucase (strcpy (tmp,test))))
+ mm_lsub (stream,NIL,s,LATT_NOINFERIORS);
+ else if (pmatch_full (s,test,'/')) mm_lsub (stream,'/',s,NIL);
+ else while (showuppers && (t = strrchr (s,'/'))) {
+ *t = '\0'; /* tie off the name */
+ if (pmatch_full (s,test,'/')) mm_lsub (stream,'/',s,LATT_NOSELECT);
+ }
+ }
+ while (s = sm_read (&sdb)); /* until no more subscriptions */
+}
+
+
+/* Dummy subscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to add to subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_subscribe (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* must be valid local mailbox */
+ if ((s = mailboxfile (tmp,mailbox)) && *s && !stat (s,&sbuf))
+ switch (sbuf.st_mode & S_IFMT) {
+ case S_IFDIR: /* allow but snarl */
+ sprintf (tmp,"CLIENT BUG DETECTED: subscribe of non-mailbox directory %.80s",
+ mailbox);
+ MM_NOTIFY (stream,tmp,WARN);
+ case S_IFREG:
+ return sm_subscribe (mailbox);
+ }
+ sprintf (tmp,"Can't subscribe %.80s: not a mailbox",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+}
+
+/* Dummy list mailboxes worker routine
+ * Accepts: mail stream
+ * directory name to search
+ * search pattern
+ * string to scan
+ * search level
+ */
+
+void dummy_list_work (MAILSTREAM *stream,char *dir,char *pat,char *contents,
+ long level)
+{
+ DRIVER *drivers;
+ dirfmttest_t dt;
+ DIR *dp;
+ struct direct *d;
+ struct stat sbuf;
+ char tmp[MAILTMPLEN],path[MAILTMPLEN];
+ size_t len = 0;
+ /* punt if bogus name */
+ if (!mailboxdir (tmp,dir,NIL)) return;
+ if (dp = opendir (tmp)) { /* do nothing if can't open directory */
+ /* see if a non-namespace directory format */
+ for (drivers = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL), dt = NIL;
+ dir && !dt && drivers; drivers = drivers->next)
+ if (!(drivers->flags & DR_DISABLE) && (drivers->flags & DR_DIRFMT) &&
+ (*drivers->valid) (dir))
+ dt = mail_parameters ((*drivers->open) (NIL),GET_DIRFMTTEST,NIL);
+ /* list it if at top-level */
+ if (!level && dir && pmatch_full (dir,pat,'/') && !pmatch (dir,"INBOX"))
+ dummy_listed (stream,'/',dir,dt ? NIL : LATT_NOSELECT,contents);
+
+ /* scan directory, ignore . and .. */
+ if (!dir || dir[(len = strlen (dir)) - 1] == '/') while (d = readdir (dp))
+ if ((!(dt && (*dt) (d->d_name))) &&
+ ((d->d_name[0] != '.') ||
+ (((long) mail_parameters (NIL,GET_HIDEDOTFILES,NIL)) ? NIL :
+ (d->d_name[1] && (((d->d_name[1] != '.') || d->d_name[2]))))) &&
+ ((len + strlen (d->d_name)) <= NETMAXMBX)) {
+ /* see if name is useful */
+ if (dir) sprintf (tmp,"%s%s",dir,d->d_name);
+ else strcpy (tmp,d->d_name);
+ /* make sure useful and can get info */
+ if ((pmatch_full (strcpy (path,tmp),pat,'/') ||
+ pmatch_full (strcat (path,"/"),pat,'/') ||
+ dmatch (path,pat,'/')) &&
+ mailboxdir (path,dir,"x") && (len = strlen (path)) &&
+ strcpy (path+len-1,d->d_name) && !stat (path,&sbuf)) {
+ /* only interested in file type */
+ switch (sbuf.st_mode & S_IFMT) {
+ case S_IFDIR: /* directory? */
+ /* form with trailing / */
+ sprintf (path,"%s/",tmp);
+ /* skip listing if INBOX */
+ if (!pmatch (tmp,"INBOX")) {
+ if (pmatch_full (tmp,pat,'/')) {
+ if (!dummy_listed (stream,'/',tmp,LATT_NOSELECT,contents))
+ break;
+ }
+ /* try again with trailing / */
+ else if (pmatch_full (path,pat,'/') &&
+ !dummy_listed (stream,'/',path,LATT_NOSELECT,contents))
+ break;
+ }
+ if (dmatch (path,pat,'/') &&
+ (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL)))
+ dummy_list_work (stream,path,pat,contents,level+1);
+ break;
+ case S_IFREG: /* ordinary name */
+ /* Must use ctime for systems that don't update mtime properly */
+ if (pmatch_full (tmp,pat,'/') && compare_cstring (tmp,"INBOX"))
+ dummy_listed (stream,'/',tmp,LATT_NOINFERIORS +
+ ((sbuf.st_size && (sbuf.st_atime < sbuf.st_ctime))?
+ LATT_MARKED : LATT_UNMARKED),contents);
+ break;
+ }
+ }
+ }
+ closedir (dp); /* all done, flush directory */
+ }
+}
+
+/* Scan file for contents
+ * Accepts: driver to use
+ * file name
+ * desired contents
+ * length of contents
+ * size of file
+ * Returns: NIL if contents not found, T if found
+ */
+
+long scan_contents (DRIVER *dtb,char *name,char *contents,
+ unsigned long csiz,unsigned long fsiz)
+{
+ scancontents_t sc = dtb ?
+ (scancontents_t) (*dtb->parameters) (GET_SCANCONTENTS,NIL) : NIL;
+ return (*(sc ? sc : dummy_scan_contents)) (name,contents,csiz,fsiz);
+}
+
+
+/* Scan file for contents
+ * Accepts: file name
+ * desired contents
+ * length of contents
+ * size of file
+ * Returns: NIL if contents not found, T if found
+ */
+
+#define BUFSIZE 4*MAILTMPLEN
+
+long dummy_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz)
+{
+ int fd;
+ unsigned long ssiz,bsiz;
+ char *buf;
+ /* forget it if can't select or open */
+ if ((fd = open (name,O_RDONLY,NIL)) >= 0) {
+ /* get buffer including slop */
+ buf = (char *) fs_get (BUFSIZE + (ssiz = 4 * ((csiz / 4) + 1)) + 1);
+ memset (buf,'\0',ssiz); /* no slop area the first time */
+ while (fsiz) { /* until end of file */
+ read (fd,buf+ssiz,bsiz = min (fsiz,BUFSIZE));
+ if (search ((unsigned char *) buf,bsiz+ssiz,
+ (unsigned char *) contents,csiz)) break;
+ memcpy (buf,buf+BUFSIZE,ssiz);
+ fsiz -= bsiz; /* note that we read that much */
+ }
+ fs_give ((void **) &buf); /* flush buffer */
+ close (fd); /* finished with file */
+ if (fsiz) return T; /* found */
+ }
+ return NIL; /* not found */
+}
+
+/* Mailbox found
+ * Accepts: MAIL stream
+ * hierarchy delimiter
+ * mailbox name
+ * attributes
+ * contents to search before calling mm_list()
+ * Returns: NIL if should abort hierarchy search, else T (currently always)
+ */
+
+long dummy_listed (MAILSTREAM *stream,char delimiter,char *name,
+ long attributes,char *contents)
+{
+ DRIVER *d;
+ DIR *dp;
+ struct direct *dr;
+ dirfmttest_t dt;
+ unsigned long csiz;
+ struct stat sbuf;
+ int nochild;
+ char *s,tmp[MAILTMPLEN];
+ if (!(attributes & LATT_NOINFERIORS) && mailboxdir (tmp,name,NIL) &&
+ (dp = opendir (tmp))) { /* if not \NoInferiors */
+ /* locate dirfmttest if any */
+ for (d = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL), dt = NIL;
+ !dt && d; d = d->next)
+ if (!(d->flags & DR_DISABLE) && (d->flags & DR_DIRFMT) &&
+ (*d->valid) (name))
+ dt = mail_parameters ((*d->open) (NIL),GET_DIRFMTTEST,NIL);
+ /* scan directory for children */
+ for (nochild = T; nochild && (dr = readdir (dp)); )
+ if ((!(dt && (*dt) (dr->d_name))) &&
+ ((dr->d_name[0] != '.') ||
+ (((long) mail_parameters (NIL,GET_HIDEDOTFILES,NIL)) ? NIL :
+ (dr->d_name[1] && ((dr->d_name[1] != '.') || dr->d_name[2])))))
+ nochild = NIL;
+ attributes |= nochild ? LATT_HASNOCHILDREN : LATT_HASCHILDREN;
+ closedir (dp); /* all done, flush directory */
+ }
+ d = NIL; /* don't \NoSelect dir if it has a driver */
+ if ((attributes & LATT_NOSELECT) && (d = mail_valid (NIL,name,NIL)) &&
+ (d != &dummydriver)) attributes &= ~LATT_NOSELECT;
+ if (!contents || /* notify main program */
+ (!(attributes & LATT_NOSELECT) && (csiz = strlen (contents)) &&
+ (s = mailboxfile (tmp,name)) &&
+ (*s || (s = mail_parameters (NIL,GET_INBOXPATH,tmp))) &&
+ !stat (s,&sbuf) && (d || (csiz <= sbuf.st_size)) &&
+ SAFE_SCAN_CONTENTS (d,tmp,contents,csiz,sbuf.st_size)))
+ mm_list (stream,delimiter,name,attributes);
+ return T;
+}
+
+/* Dummy create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,tmp[MAILTMPLEN];
+ long ret = NIL;
+ /* validate name */
+ if (!(compare_cstring (mailbox,"INBOX") && (s = dummy_file (tmp,mailbox)))) {
+ sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
+ MM_LOG (tmp,ERROR);
+ }
+ /* create the name, done if made directory */
+ else if ((ret = dummy_create_path (stream,tmp,get_dir_protection(mailbox)))&&
+ (s = strrchr (s,'/')) && !s[1]) return T;
+ return ret ? set_mbx_protections (mailbox,tmp) : NIL;
+}
+
+/* Dummy create path
+ * Accepts: mail stream
+ * path name to create
+ * directory mode
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode)
+{
+ struct stat sbuf;
+ char c,*s,tmp[MAILTMPLEN];
+ int fd;
+ long ret = NIL;
+ char *t = strrchr (path,'/');
+ int wantdir = t && !t[1];
+ int mask = umask (0);
+ if (wantdir) *t = '\0'; /* flush trailing delimiter for directory */
+ if (s = strrchr (path,'/')) { /* found superior to this name? */
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (path,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,path,dirmode)) {
+ umask (mask); /* restore mask */
+ return NIL;
+ }
+ *s = c; /* restore full name */
+ }
+ if (wantdir) { /* want to create directory? */
+ ret = !mkdir (path,(int) dirmode);
+ *t = '/'; /* restore directory delimiter */
+ }
+ /* create file */
+ else if ((fd = open (path,O_WRONLY|O_CREAT|O_EXCL,
+ (long) mail_parameters(NIL,GET_MBXPROTECTION,NIL))) >=0)
+ ret = !close (fd);
+ if (!ret) { /* error? */
+ sprintf (tmp,"Can't create mailbox node %.80s: %.80s",path,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ umask (mask); /* restore mask */
+ return ret; /* return status */
+}
+
+/* Dummy delete mailbox
+ * Accepts: mail stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_delete (MAILSTREAM *stream,char *mailbox)
+{
+ struct stat sbuf;
+ char *s,tmp[MAILTMPLEN];
+ if (!(s = dummy_file (tmp,mailbox))) {
+ sprintf (tmp,"Can't delete - invalid name: %.80s",s);
+ MM_LOG (tmp,ERROR);
+ }
+ /* no trailing / (workaround BSD kernel bug) */
+ if ((s = strrchr (tmp,'/')) && !s[1]) *s = '\0';
+ if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) == S_IFDIR) ?
+ rmdir (tmp) : unlink (tmp)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %.80s",mailbox,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ return T; /* return success */
+}
+
+/* Mail rename mailbox
+ * Accepts: mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ struct stat sbuf;
+ char c,*s,tmp[MAILTMPLEN],mbx[MAILTMPLEN],oldname[MAILTMPLEN];
+ /* no trailing / allowed */
+ if (!dummy_file (oldname,old) || !(s = dummy_file (mbx,newname)) ||
+ stat (oldname,&sbuf) || ((s = strrchr (s,'/')) && !s[1] &&
+ ((sbuf.st_mode & S_IFMT) != S_IFDIR))) {
+ sprintf (mbx,"Can't rename %.80s to %.80s: invalid name",old,newname);
+ MM_LOG (mbx,ERROR);
+ return NIL;
+ }
+ if (s) { /* found a directory delimiter? */
+ if (!s[1]) *s = '\0'; /* ignore trailing delimiter */
+ else { /* found superior to destination name? */
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (mbx,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create (stream,mbx)) return NIL;
+ *s = c; /* restore full name */
+ }
+ }
+ /* rename of non-ex INBOX creates dest */
+ if (!compare_cstring (old,"INBOX") && stat (oldname,&sbuf))
+ return dummy_create (NIL,mbx);
+ if (rename (oldname,mbx)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",old,newname,
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ return T; /* return success */
+}
+
+/* Dummy open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *dummy_open (MAILSTREAM *stream)
+{
+ int fd;
+ char err[MAILTMPLEN],tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* OP_PROTOTYPE call */
+ if (!stream) return &dummyproto;
+ err[0] = '\0'; /* no error message yet */
+ /* can we open the file? */
+ if (!dummy_file (tmp,stream->mailbox))
+ sprintf (err,"Can't open this name: %.80s",stream->mailbox);
+ else if ((fd = open (tmp,O_RDONLY,NIL)) < 0) {
+ /* no, error unless INBOX */
+ if (compare_cstring (stream->mailbox,"INBOX"))
+ sprintf (err,"%.80s: %.80s",strerror (errno),stream->mailbox);
+ }
+ else { /* file had better be empty then */
+ fstat (fd,&sbuf); /* sniff at its size */
+ close (fd);
+ if ((sbuf.st_mode & S_IFMT) != S_IFREG)
+ sprintf (err,"Can't open %.80s: not a selectable mailbox",
+ stream->mailbox);
+ else if (sbuf.st_size) /* bogus format if non-empty */
+ sprintf (err,"Can't open %.80s (file %.80s): not in valid mailbox format",
+ stream->mailbox,tmp);
+ }
+ if (err[0]) { /* if an error happened */
+ MM_LOG (err,stream->silent ? WARN : ERROR);
+ return NIL;
+ }
+ else if (!stream->silent) { /* only if silence not requested */
+ mail_exists (stream,0); /* say there are 0 messages */
+ mail_recent (stream,0); /* and certainly no recent ones! */
+ stream->uid_validity = time (0);
+ }
+ stream->inbox = T; /* note that it's an INBOX */
+ return stream; /* return success */
+}
+
+
+/* Dummy close
+ * Accepts: MAIL stream
+ * options
+ */
+
+void dummy_close (MAILSTREAM *stream,long options)
+{
+ /* return silently */
+}
+
+/* Dummy ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long dummy_ping (MAILSTREAM *stream)
+{
+ MAILSTREAM *test;
+ if (time (0) >= /* time to do another test? */
+ ((time_t) (stream->gensym +
+ (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL)))) {
+ /* has mailbox format changed? */
+ if ((test = mail_open (NIL,stream->mailbox,OP_PROTOTYPE)) &&
+ (test->dtb != stream->dtb) &&
+ (test = mail_open (NIL,stream->mailbox,NIL))) {
+ /* preserve some resources */
+ test->original_mailbox = stream->original_mailbox;
+ stream->original_mailbox = NIL;
+ test->sparep = stream->sparep;
+ stream->sparep = NIL;
+ test->sequence = stream->sequence;
+ mail_close ((MAILSTREAM *) /* flush resources used by dummy stream */
+ memcpy (fs_get (sizeof (MAILSTREAM)),stream,
+ sizeof (MAILSTREAM)));
+ /* swap the streams */
+ memcpy (stream,test,sizeof (MAILSTREAM));
+ fs_give ((void **) &test);/* flush test now that copied */
+ /* make sure application knows */
+ mail_exists (stream,stream->recent = stream->nmsgs);
+ }
+ /* still hasn't changed */
+ else stream->gensym = time (0);
+ }
+ return T;
+}
+
+
+/* Dummy check mailbox
+ * Accepts: MAIL stream
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+void dummy_check (MAILSTREAM *stream)
+{
+ dummy_ping (stream); /* invoke ping */
+}
+
+
+/* Dummy expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ return LONGT;
+}
+
+/* Dummy copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * options
+ * Returns: T if copy successful, else NIL
+ */
+
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) fatal ("Impossible dummy_copy");
+ return NIL;
+}
+
+
+/* Dummy append message string
+ * Accepts: mail stream
+ * destination mailbox
+ * append callback function
+ * data for callback
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd = -1;
+ int e;
+ char tmp[MAILTMPLEN];
+ MAILSTREAM *ts = default_proto (T);
+ /* append to INBOX? */
+ if (!compare_cstring (mailbox,"INBOX")) {
+ /* yes, if no empty proto try creating */
+ if (!ts && !(*(ts = default_proto (NIL))->dtb->create) (ts,"INBOX"))
+ ts = NIL;
+ }
+ else if (dummy_file (tmp,mailbox) && ((fd = open (tmp,O_RDONLY,NIL)) < 0)) {
+ if ((e = errno) == ENOENT) /* failed, was it no such file? */
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ sprintf (tmp,"%.80s: %.80s",strerror (e),mailbox);
+ MM_LOG (tmp,ERROR); /* pass up error */
+ return NIL; /* always fails */
+ }
+ else if (fd >= 0) { /* found file? */
+ fstat (fd,&sbuf); /* get its size */
+ close (fd); /* toss out the fd */
+ if (sbuf.st_size) ts = NIL; /* non-empty file? */
+ }
+ if (ts) return (*ts->dtb->append) (stream,mailbox,af,data);
+ sprintf (tmp,"Indeterminate mailbox format: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+}
+
+/* Dummy mail generate file string
+ * Accepts: temporary buffer to write into
+ * mailbox name string
+ * Returns: local file string or NIL if failure
+ */
+
+char *dummy_file (char *dst,char *name)
+{
+ char *s = mailboxfile (dst,name);
+ /* return our standard inbox */
+ return (s && !*s) ? strcpy (dst,sysinbox ()) : s;
+}
+
+
+/* Dummy canonicalize name
+ * Accepts: buffer to write name
+ * reference
+ * pattern
+ * Returns: T if success, NIL if failure
+ */
+
+long dummy_canonicalize (char *tmp,char *ref,char *pat)
+{
+ unsigned long i;
+ char *s;
+ if (ref) { /* preliminary reference check */
+ if (*ref == '{') return NIL;/* remote reference not allowed */
+ else if (!*ref) ref = NIL; /* treat empty reference as no reference */
+ }
+ switch (*pat) {
+ case '#': /* namespace name */
+ if (mailboxfile (tmp,pat)) strcpy (tmp,pat);
+ else return NIL; /* unknown namespace */
+ break;
+ case '{': /* remote names not allowed */
+ return NIL;
+ case '/': /* rooted name */
+ case '~': /* home directory name */
+ if (!ref || (*ref != '#')) {/* non-namespace reference? */
+ strcpy (tmp,pat); /* yes, ignore */
+ break;
+ }
+ /* fall through */
+ default: /* apply reference for all other names */
+ if (!ref) strcpy (tmp,pat); /* just copy if no namespace */
+ else if ((*ref != '#') || mailboxfile (tmp,ref)) {
+ /* wants root of name? */
+ if (*pat == '/') strcpy (strchr (strcpy (tmp,ref),'/'),pat);
+ /* otherwise just append */
+ else sprintf (tmp,"%s%s",ref,pat);
+ }
+ else return NIL; /* unknown namespace */
+ }
+ /* count wildcards */
+ for (i = 0, s = tmp; *s; *s++) if ((*s == '*') || (*s == '%')) ++i;
+ if (i > MAXWILDCARDS) { /* ridiculous wildcarding? */
+ MM_LOG ("Excessive wildcards in LIST/LSUB",ERROR);
+ return NIL;
+ }
+ return T;
+}
diff --git a/imap/src/osdep/amiga/dummy.h b/imap/src/osdep/amiga/dummy.h
new file mode 100644
index 00000000..32650e06
--- /dev/null
+++ b/imap/src/osdep/amiga/dummy.h
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 9 May 1991
+ * Last Edited: 30 August 2006
+ */
+
+/* Exported function prototypes */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat);
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long scan_contents (DRIVER *dtb,char *name,char *contents,
+ unsigned long csiz,unsigned long fsiz);
+long dummy_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz);
+long dummy_create (MAILSTREAM *stream,char *mailbox);
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode);
+long dummy_delete (MAILSTREAM *stream,char *mailbox);
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname);
+char *dummy_file (char *dst,char *name);
+long dummy_canonicalize (char *tmp,char *ref,char *pat);
diff --git a/imap/src/osdep/amiga/env_ami.c b/imap/src/osdep/amiga/env_ami.c
new file mode 100644
index 00000000..843757ed
--- /dev/null
+++ b/imap/src/osdep/amiga/env_ami.c
@@ -0,0 +1,1282 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Amiga environment routines
+ *
+ * Author: Mark Crispin
+ * UW Technology
+ * Seattle, WA 98195
+ * Internet: MRC@Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 15 May 2008
+ */
+
+#include <grp.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+/* c-client environment parameters */
+
+static char *myUserName = NIL; /* user name */
+static char *myHomeDir = NIL; /* home directory name */
+static char *myMailboxDir = NIL;/* mailbox directory name */
+static char *myLocalHost = NIL; /* local host name */
+static char *myNewsrc = NIL; /* newsrc file name */
+static char *mailsubdir = NIL; /* mail subdirectory name */
+static char *sysInbox = NIL; /* system inbox name */
+static char *newsActive = NIL; /* news active file */
+static char *newsSpool = NIL; /* news spool */
+ /* anonymous home directory */
+static char *anonymousHome = NIL;
+static char *ftpHome = NIL; /* ftp export home directory */
+static char *publicHome = NIL; /* public home directory */
+static char *sharedHome = NIL; /* shared home directory */
+static short anonymous = NIL; /* is anonymous */
+static short restrictBox = NIL; /* is a restricted box */
+static short has_no_life = NIL; /* is a cretin with no life */
+ /* block environment init */
+static short block_env_init = NIL;
+static short hideDotFiles = NIL;/* hide files whose names start with . */
+ /* advertise filesystem root */
+static short advertisetheworld = NIL;
+ /* disable automatic shared namespaces */
+static short noautomaticsharedns = NIL;
+static short no822tztext = NIL; /* disable RFC [2]822 timezone text */
+static short netfsstatbug = NIL;/* compensate for broken stat() on network
+ * filesystems (AFS and old NFS). Don't do
+ * this unless you really have to!
+ */
+ /* 1 = disable plaintext, 2 = if not SSL */
+static long disablePlaintext = NIL;
+static long list_max_level = 20;/* maximum level of list recursion */
+ /* default file protection */
+static long mbx_protection = 0600;
+ /* default directory protection */
+static long dir_protection = 0700;
+ /* default lock file protection */
+static long lock_protection = MANDATORYLOCKPROT;
+ /* default ftp file protection */
+static long ftp_protection = 0644;
+static long ftp_dir_protection = 0755;
+ /* default public file protection */
+static long public_protection = 0666;
+static long public_dir_protection = 0777;
+ /* default shared file protection */
+static long shared_protection = 0660;
+static long shared_dir_protection = 0770;
+static long locktimeout = 5; /* default lock timeout */
+ /* default prototypes */
+static MAILSTREAM *createProto = NIL;
+static MAILSTREAM *appendProto = NIL;
+ /* default user flags */
+static char *userFlags[NUSERFLAGS] = {NIL};
+static NAMESPACE *nslist[3]; /* namespace list */
+static int logtry = 3; /* number of server login tries */
+ /* block notification */
+static blocknotify_t mailblocknotify = mm_blocknotify;
+
+/* Amiga namespaces */
+
+ /* personal mh namespace */
+static NAMESPACE nsmhf = {"#mh/",'/',NIL,NIL};
+static NAMESPACE nsmh = {"#mhinbox",NIL,NIL,&nsmhf};
+ /* home namespace */
+static NAMESPACE nshome = {"",'/',NIL,&nsmh};
+ /* Amiga other user namespace */
+static NAMESPACE nsamigaother = {"~",'/',NIL,NIL};
+ /* public (anonymous OK) namespace */
+static NAMESPACE nspublic = {"#public/",'/',NIL,NIL};
+ /* netnews namespace */
+static NAMESPACE nsnews = {"#news.",'.',NIL,&nspublic};
+ /* FTP export namespace */
+static NAMESPACE nsftp = {"#ftp/",'/',NIL,&nsnews};
+ /* shared (no anonymous) namespace */
+static NAMESPACE nsshared = {"#shared/",'/',NIL,&nsftp};
+ /* world namespace */
+static NAMESPACE nsworld = {"/",'/',NIL,&nsshared};
+
+#include "write.c" /* include safe writing routines */
+#include "pmatch.c" /* include wildcard pattern matcher */
+
+/* Get all authenticators */
+
+#include "auths.c"
+
+/* Environment manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *env_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_NAMESPACE:
+ ret = (void *) nslist;
+ break;
+ case SET_USERNAME:
+ if (myUserName) fs_give ((void **) &myUserName);
+ myUserName = cpystr ((char *) value);
+ case GET_USERNAME:
+ ret = (void *) myUserName;
+ break;
+ case SET_HOMEDIR:
+ if (myHomeDir) fs_give ((void **) &myHomeDir);
+ myHomeDir = cpystr ((char *) value);
+ case GET_HOMEDIR:
+ ret = (void *) myHomeDir;
+ break;
+ case SET_LOCALHOST:
+ if (myLocalHost) fs_give ((void **) &myLocalHost);
+ myLocalHost = cpystr ((char *) value);
+ case GET_LOCALHOST:
+ ret = (void *) myLocalHost;
+ break;
+ case SET_NEWSRC:
+ if (myNewsrc) fs_give ((void **) &myNewsrc);
+ myNewsrc = cpystr ((char *) value);
+ case GET_NEWSRC:
+ ret = (void *) myNewsrc;
+ break;
+ case SET_NEWSACTIVE:
+ if (newsActive) fs_give ((void **) &newsActive);
+ newsActive = cpystr ((char *) value);
+ case GET_NEWSACTIVE:
+ ret = (void *) newsActive;
+ break;
+ case SET_NEWSSPOOL:
+ if (newsSpool) fs_give ((void **) &newsSpool);
+ newsSpool = cpystr ((char *) value);
+ case GET_NEWSSPOOL:
+ ret = (void *) newsSpool;
+ break;
+
+ case SET_ANONYMOUSHOME:
+ if (anonymousHome) fs_give ((void **) &anonymousHome);
+ anonymousHome = cpystr ((char *) value);
+ case GET_ANONYMOUSHOME:
+ if (!anonymousHome) anonymousHome = cpystr (ANONYMOUSHOME);
+ ret = (void *) anonymousHome;
+ break;
+ case SET_FTPHOME:
+ if (ftpHome) fs_give ((void **) &ftpHome);
+ ftpHome = cpystr ((char *) value);
+ case GET_FTPHOME:
+ ret = (void *) ftpHome;
+ break;
+ case SET_PUBLICHOME:
+ if (publicHome) fs_give ((void **) &publicHome);
+ publicHome = cpystr ((char *) value);
+ case GET_PUBLICHOME:
+ ret = (void *) publicHome;
+ break;
+ case SET_SHAREDHOME:
+ if (sharedHome) fs_give ((void **) &sharedHome);
+ sharedHome = cpystr ((char *) value);
+ case GET_SHAREDHOME:
+ ret = (void *) sharedHome;
+ break;
+ case SET_SYSINBOX:
+ if (sysInbox) fs_give ((void **) &sysInbox);
+ sysInbox = cpystr ((char *) value);
+ case GET_SYSINBOX:
+ ret = (void *) sysInbox;
+ break;
+ case SET_LISTMAXLEVEL:
+ list_max_level = (long) value;
+ case GET_LISTMAXLEVEL:
+ ret = (void *) list_max_level;
+ break;
+
+ case SET_MBXPROTECTION:
+ mbx_protection = (long) value;
+ case GET_MBXPROTECTION:
+ ret = (void *) mbx_protection;
+ break;
+ case SET_DIRPROTECTION:
+ dir_protection = (long) value;
+ case GET_DIRPROTECTION:
+ ret = (void *) dir_protection;
+ break;
+ case SET_LOCKPROTECTION:
+ lock_protection = (long) value;
+ case GET_LOCKPROTECTION:
+ ret = (void *) lock_protection;
+ break;
+ case SET_FTPPROTECTION:
+ ftp_protection = (long) value;
+ case GET_FTPPROTECTION:
+ ret = (void *) ftp_protection;
+ break;
+ case SET_PUBLICPROTECTION:
+ public_protection = (long) value;
+ case GET_PUBLICPROTECTION:
+ ret = (void *) public_protection;
+ break;
+ case SET_SHAREDPROTECTION:
+ shared_protection = (long) value;
+ case GET_SHAREDPROTECTION:
+ ret = (void *) shared_protection;
+ break;
+ case SET_FTPDIRPROTECTION:
+ ftp_dir_protection = (long) value;
+ case GET_FTPDIRPROTECTION:
+ ret = (void *) ftp_dir_protection;
+ break;
+ case SET_PUBLICDIRPROTECTION:
+ public_dir_protection = (long) value;
+ case GET_PUBLICDIRPROTECTION:
+ ret = (void *) public_dir_protection;
+ break;
+ case SET_SHAREDDIRPROTECTION:
+ shared_dir_protection = (long) value;
+ case GET_SHAREDDIRPROTECTION:
+ ret = (void *) shared_dir_protection;
+ break;
+
+ case SET_LOCKTIMEOUT:
+ locktimeout = (long) value;
+ case GET_LOCKTIMEOUT:
+ ret = (void *) locktimeout;
+ break;
+ case SET_HIDEDOTFILES:
+ hideDotFiles = value ? T : NIL;
+ case GET_HIDEDOTFILES:
+ ret = (void *) (hideDotFiles ? VOIDT : NIL);
+ break;
+ case SET_DISABLEPLAINTEXT:
+ disablePlaintext = (long) value;
+ case GET_DISABLEPLAINTEXT:
+ ret = (void *) disablePlaintext;
+ break;
+ case SET_ADVERTISETHEWORLD:
+ advertisetheworld = value ? T : NIL;
+ case GET_ADVERTISETHEWORLD:
+ ret = (void *) (advertisetheworld ? VOIDT : NIL);
+ break;
+ case SET_DISABLEAUTOSHAREDNS:
+ noautomaticsharedns = value ? T : NIL;
+ case GET_DISABLEAUTOSHAREDNS:
+ ret = (void *) (noautomaticsharedns ? VOIDT : NIL);
+ break;
+ case SET_DISABLE822TZTEXT:
+ no822tztext = value ? T : NIL;
+ case GET_DISABLE822TZTEXT:
+ ret = (void *) (no822tztext ? VOIDT : NIL);
+ break;
+ case SET_USERHASNOLIFE:
+ has_no_life = value ? T : NIL;
+ case GET_USERHASNOLIFE:
+ ret = (void *) (has_no_life ? VOIDT : NIL);
+ break;
+ case SET_NETFSSTATBUG:
+ netfsstatbug = value ? T : NIL;
+ case GET_NETFSSTATBUG:
+ ret = (void *) (netfsstatbug ? VOIDT : NIL);
+ break;
+ case SET_BLOCKENVINIT:
+ block_env_init = value ? T : NIL;
+ case GET_BLOCKENVINIT:
+ ret = (void *) (block_env_init ? VOIDT : NIL);
+ break;
+ case SET_BLOCKNOTIFY:
+ mailblocknotify = (blocknotify_t) value;
+ case GET_BLOCKNOTIFY:
+ ret = (void *) mailblocknotify;
+ break;
+ }
+ return ret;
+}
+
+/* Write current time
+ * Accepts: destination string
+ * optional format of day-of-week prefix
+ * format of date and time
+ * flag whether to append symbolic timezone
+ */
+
+static void do_date (char *date,char *prefix,char *fmt,int suffix)
+{
+ time_t tn = time (0);
+ struct tm *t = gmtime (&tn);
+ int zone = t->tm_hour * 60 + t->tm_min;
+ int julian = t->tm_yday;
+ t = localtime (&tn); /* get local time now */
+ /* minus UTC minutes since midnight */
+ zone = t->tm_hour * 60 + t->tm_min - zone;
+ /* julian can be one of:
+ * 36x local time is December 31, UTC is January 1, offset -24 hours
+ * 1 local time is 1 day ahead of UTC, offset +24 hours
+ * 0 local time is same day as UTC, no offset
+ * -1 local time is 1 day behind UTC, offset -24 hours
+ * -36x local time is January 1, UTC is December 31, offset +24 hours
+ */
+ if (julian = t->tm_yday -julian)
+ zone += ((julian < 0) == (abs (julian) == 1)) ? -24*60 : 24*60;
+ if (prefix) { /* want day of week? */
+ sprintf (date,prefix,days[t->tm_wday]);
+ date += strlen (date); /* make next sprintf append */
+ }
+ /* output the date */
+ sprintf (date,fmt,t->tm_mday,months[t->tm_mon],t->tm_year+1900,
+ t->tm_hour,t->tm_min,t->tm_sec,zone/60,abs (zone) % 60);
+ /* append timezone suffix if desired */
+ if (suffix) rfc822_timezone (date,(void *) t);
+}
+
+/* Write current time in RFC 822 format
+ * Accepts: destination string
+ */
+
+void rfc822_date (char *date)
+{
+ do_date (date,"%s, ","%d %s %d %02d:%02d:%02d %+03d%02d",
+ no822tztext ? NIL : T);
+}
+
+
+/* Write current time in fixed-width RFC 822 format
+ * Accepts: destination string
+ */
+
+void rfc822_fixed_date (char *date)
+{
+ do_date (date,NIL,"%02d %s %4d %02d:%02d:%02d %+03d%02d",NIL);
+}
+
+
+/* Write current time in internal format
+ * Accepts: destination string
+ */
+
+void internal_date (char *date)
+{
+ do_date (date,NIL,"%02d-%s-%d %02d:%02d:%02d %+03d%02d",NIL);
+}
+
+/* Initialize server
+ * Accepts: server name for syslog or NIL
+ * /etc/services service name or NIL
+ * alternate /etc/services service name or NIL
+ * clock interrupt handler
+ * kiss-of-death interrupt handler
+ * hangup interrupt handler
+ * termination interrupt handler
+ */
+
+void server_init (char *server,char *service,char *sslservice,
+ void *clkint,void *kodint,void *hupint,void *trmint,
+ void *staint)
+{
+ int onceonly = server && service && sslservice;
+ if (onceonly) { /* set server name in syslog */
+ int mask;
+ openlog (myServerName = cpystr (server),LOG_PID,syslog_facility);
+ fclose (stderr); /* possibly save a process ID */
+
+ switch (mask = umask (022)){/* check old umask */
+ case 0: /* definitely unreasonable */
+ case 022: /* don't need to change it */
+ break;
+ default: /* already was a reasonable value */
+ umask (mask); /* so change it back */
+ }
+ }
+ arm_signal (SIGALRM,clkint); /* prepare for clock interrupt */
+ arm_signal (SIGUSR2,kodint); /* prepare for Kiss Of Death */
+ arm_signal (SIGHUP,hupint); /* prepare for hangup */
+ arm_signal (SIGPIPE,hupint); /* alternative hangup */
+ arm_signal (SIGTERM,trmint); /* prepare for termination */
+ /* status dump */
+ if (staint) arm_signal (SIGUSR1,staint);
+ if (onceonly) { /* set up network and maybe SSL */
+ long port;
+ struct servent *sv;
+ /* Use SSL if SSL service, or if server starts with "s" and not service */
+ if (((port = tcp_serverport ()) >= 0)) {
+ if ((sv = getservbyname (service,"tcp")) && (port == ntohs (sv->s_port)))
+ syslog (LOG_DEBUG,"%s service init from %s",service,tcp_clientaddr ());
+ else if ((sv = getservbyname (sslservice,"tcp")) &&
+ (port == ntohs (sv->s_port))) {
+ syslog (LOG_DEBUG,"%s SSL service init from %s",sslservice,
+ tcp_clientaddr ());
+ ssl_server_init (server);
+ }
+ else { /* not service or SSL service port */
+ syslog (LOG_DEBUG,"port %ld service init from %s",port,
+ tcp_clientaddr ());
+ if (*server == 's') ssl_server_init (server);
+ }
+ }
+ }
+}
+
+/* Wait for stdin input
+ * Accepts: timeout in seconds
+ * Returns: T if have input on stdin, else NIL
+ */
+
+long server_input_wait (long seconds)
+{
+ fd_set rfd,efd;
+ struct timeval tmo;
+ FD_ZERO (&rfd);
+ FD_ZERO (&efd);
+ FD_SET (0,&rfd);
+ FD_SET (0,&efd);
+ tmo.tv_sec = seconds; tmo.tv_usec = 0;
+ return select (1,&rfd,0,&efd,&tmo) ? LONGT : NIL;
+}
+
+/* Return Amiga password entry for user name
+ * Accepts: user name string
+ * Returns: password entry
+ *
+ * Tries all-lowercase form of user name if given user name fails
+ */
+
+static struct passwd *pwuser (unsigned char *user)
+{
+ unsigned char *s;
+ struct passwd *pw = getpwnam (user);
+ if (!pw) { /* failed, see if any uppercase characters */
+ for (s = user; *s && ((*s < 'A') || (*s > 'Z')); s++);
+ if (*s) { /* yes, try all lowercase form */
+ pw = getpwnam (s = lcase (cpystr (user)));
+ fs_give ((void **) &s);
+ }
+ }
+ return pw;
+}
+
+
+/* Validate password for user name
+ * Accepts: user name string
+ * password string
+ * argument count
+ * argument vector
+ * Returns: password entry if validated
+ *
+ * Tries password+1 if password fails and starts with space
+ */
+
+static struct passwd *valpwd (char *user,char *pwd,int argc,char *argv[])
+{
+ char *s;
+ struct passwd *pw;
+ struct passwd *ret = NIL;
+ if (auth_md5.server) { /* using CRAM-MD5 authentication? */
+ if (s = auth_md5_pwd (user)) {
+ if (!strcmp (s,pwd) || ((*pwd == ' ') && pwd[1] && !strcmp (s,pwd+1)))
+ ret = pwuser (user); /* validated, get passwd entry for user */
+ memset (s,0,strlen (s)); /* erase sensitive information */
+ fs_give ((void **) &s);
+ }
+ }
+ else if (pw = pwuser (user)) {/* can get user? */
+ s = cpystr (pw->pw_name); /* copy returned name in case we need it */
+ if (*pwd && !(ret = checkpw (pw,pwd,argc,argv)) &&
+ (*pwd == ' ') && pwd[1] && (ret = pwuser (s)))
+ ret = checkpw (pw,pwd+1,argc,argv);
+ fs_give ((void **) &s); /* don't need copy of name any more */
+ }
+ return ret;
+}
+
+/* Server log in
+ * Accepts: user name string
+ * password string
+ * authenticating user name string
+ * argument count
+ * argument vector
+ * Returns: T if password validated, NIL otherwise
+ */
+
+long server_login (char *user,char *pwd,char *authuser,int argc,char *argv[])
+{
+ struct passwd *pw = NIL;
+ int level = LOG_NOTICE;
+ char *err = "failed";
+ /* cretins still haven't given up */
+ if ((strlen (user) >= NETMAXUSER) ||
+ (authuser && (strlen (authuser) >= NETMAXUSER))) {
+ level = LOG_ALERT; /* escalate this alert */
+ err = "SYSTEM BREAK-IN ATTEMPT";
+ logtry = 0; /* render this session useless */
+ }
+ else if (logtry-- <= 0) err = "excessive login failures";
+ else if (disablePlaintext) err = "disabled";
+ else if (!(authuser && *authuser)) pw = valpwd (user,pwd,argc,argv);
+ else if (valpwd (authuser,pwd,argc,argv)) pw = pwuser (user);
+ if (pw && pw_login (pw,authuser,pw->pw_name,NIL,argc,argv)) return T;
+ syslog (level|LOG_AUTH,"Login %s user=%.64s auth=%.64s host=%.80s",err,
+ user,(authuser && *authuser) ? authuser : user,tcp_clienthost ());
+ sleep (3); /* slow down possible cracker */
+ return NIL;
+}
+
+/* Authenticated server log in
+ * Accepts: user name string
+ * authenticating user name string
+ * argument count
+ * argument vector
+ * Returns: T if password validated, NIL otherwise
+ */
+
+long authserver_login (char *user,char *authuser,int argc,char *argv[])
+{
+ return pw_login (pwuser (user),authuser,user,NIL,argc,argv);
+}
+
+
+/* Log in as anonymous daemon
+ * Accepts: argument count
+ * argument vector
+ * Returns: T if successful, NIL if error
+ */
+
+long anonymous_login (int argc,char *argv[])
+{
+ /* log in Mr. A. N. Onymous */
+ return pw_login (getpwnam (ANONYMOUSUSER),NIL,NIL,
+ (char *) mail_parameters (NIL,GET_ANONYMOUSHOME,NIL),
+ argc,argv);
+}
+
+/* Finish log in and environment initialization
+ * Accepts: passwd struct for loginpw()
+ * optional authentication user name
+ * user name (NIL for anonymous)
+ * home directory (NIL to use directory from passwd struct)
+ * argument count
+ * argument vector
+ * Returns: T if successful, NIL if error
+ */
+
+long pw_login (struct passwd *pw,char *auser,char *user,char *home,int argc,
+ char *argv[])
+{
+ struct group *gr;
+ char **t;
+ long ret = NIL;
+ if (pw && pw->pw_uid) { /* must have passwd struct for non-UID 0 */
+ /* make safe copies of user and home */
+ if (user) user = cpystr (pw->pw_name);
+ home = cpystr (home ? home : pw->pw_dir);
+ /* authorization ID .NE. authentication ID? */
+ if (user && auser && *auser && compare_cstring (auser,user)) {
+ /* scan list of mail administrators */
+ if ((gr = getgrnam (ADMINGROUP)) && (t = gr->gr_mem)) while (*t && !ret)
+ if (!compare_cstring (auser,*t++))
+ ret = pw_login (pw,NIL,user,home,argc,argv);
+ syslog (LOG_NOTICE|LOG_AUTH,"%s %.80s override of user=%.80s host=%.80s",
+ ret ? "Admin" : "Failed",auser,user,tcp_clienthost ());
+ }
+ /* normal login */
+ else if (((pw->pw_uid == geteuid ()) || loginpw (pw,argc,argv)) &&
+ (ret = env_init (user,home))) chdir (myhomedir ());
+ fs_give ((void **) &home); /* clean up */
+ if (user) fs_give ((void **) &user);
+ }
+ return ret; /* return status */
+}
+
+/* Initialize environment
+ * Accepts: user name (NIL for anonymous)
+ * home directory name
+ * Returns: T, always
+ */
+
+long env_init (char *user,char *home)
+{
+ extern MAILSTREAM CREATEPROTO;
+ extern MAILSTREAM EMPTYPROTO;
+ struct passwd *pw;
+ struct stat sbuf;
+ char tmp[MAILTMPLEN];
+ /* don't init if blocked */
+ if (block_env_init) return LONGT;
+ if (myUserName) fatal ("env_init called twice!");
+ /* set up user name */
+ myUserName = cpystr (user ? user : ANONYMOUSUSER);
+ if (user) { /* remember user name and home directory */
+ nslist[0] = &nshome; /* home namespace */
+ nslist[1] = &nsamigaother;
+ nslist[2] = advertisetheworld ? &nsworld : &nsshared;
+ }
+ else { /* anonymous user */
+ nslist[0] = nslist[1] = NIL,nslist[2] = &nsftp;
+ sprintf (tmp,"%s/INBOX",
+ home = (char *) mail_parameters (NIL,GET_ANONYMOUSHOME,NIL));
+ sysInbox = cpystr (tmp); /* make system INBOX */
+ anonymous = T; /* flag as anonymous */
+ }
+ myHomeDir = cpystr (home); /* set home directory */
+ if (!noautomaticsharedns) {
+ /* #ftp namespace */
+ if (!ftpHome && (pw = getpwnam ("ftp"))) ftpHome = cpystr (pw->pw_dir);
+ /* #public namespace */
+ if (!publicHome && (pw = getpwnam ("imappublic")))
+ publicHome = cpystr (pw->pw_dir);
+ /* #shared namespace */
+ if (!anonymous && !sharedHome && (pw = getpwnam ("imapshared")))
+ sharedHome = cpystr (pw->pw_dir);
+ }
+ if (!myLocalHost) mylocalhost ();
+ if (!myNewsrc) myNewsrc = cpystr(strcat (strcpy (tmp,myHomeDir),"/.newsrc"));
+ if (!newsActive) newsActive = cpystr (ACTIVEFILE);
+ if (!newsSpool) newsSpool = cpystr (NEWSSPOOL);
+ /* force default prototype to be set */
+ if (!createProto) createProto = &CREATEPROTO;
+ if (!appendProto) appendProto = &EMPTYPROTO;
+ /* re-do open action to get flags */
+ (*createProto->dtb->open) (NIL);
+ endpwent (); /* close pw database */
+ return T;
+}
+
+/* Return my user name
+ * Accepts: pointer to optional flags
+ * Returns: my user name
+ */
+
+char *myusername_full (unsigned long *flags)
+{
+ struct passwd *pw;
+ struct stat sbuf;
+ char *s;
+ unsigned long euid;
+ char *ret = UNLOGGEDUSER;
+ /* no user name yet and not root? */
+ if (!myUserName && (euid = geteuid ())) {
+ /* yes, look up getlogin() user name or EUID */
+ if (((s = (char *) getlogin ()) && *s && (strlen (s) < NETMAXUSER) &&
+ (pw = getpwnam (s)) && (pw->pw_uid == euid)) ||
+ (pw = getpwuid (euid))) {
+ if (block_env_init) { /* don't env_init if blocked */
+ if (flags) *flags = MU_LOGGEDIN;
+ return pw->pw_name;
+ }
+ env_init (pw->pw_name,
+ ((s = getenv ("HOME")) && *s && (strlen (s) < NETMAXMBX) &&
+ !stat (s,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) ?
+ s : pw->pw_dir);
+ }
+ else fatal ("Unable to look up user name");
+ }
+ if (myUserName) { /* logged in? */
+ if (flags) *flags = anonymous ? MU_ANONYMOUS : MU_LOGGEDIN;
+ ret = myUserName; /* return user name */
+ }
+ else if (flags) *flags = MU_NOTLOGGEDIN;
+ return ret;
+}
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost ()
+{
+ char tmp[MAILTMPLEN];
+ struct hostent *host_name;
+ if (!myLocalHost) myLocalHost = cpystr (gethostname (tmp,MAILTMPLEN-1) ?
+ "random-pc" : tcp_canonical (tmp));
+ return myLocalHost;
+}
+
+/* Return my home directory name
+ * Returns: my home directory name
+ */
+
+char *myhomedir ()
+{
+ if (!myHomeDir) myusername ();/* initialize if first time */
+ return myHomeDir ? myHomeDir : "";
+}
+
+
+/* Return my home mailbox name
+ * Returns: my home directory name
+ */
+
+static char *mymailboxdir ()
+{
+ char *home = myhomedir ();
+ if (!myMailboxDir && home) { /* initialize if first time */
+ if (mailsubdir) {
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"%s/%s",home,mailsubdir);
+ myMailboxDir = cpystr (tmp);/* use pre-defined subdirectory of home */
+ }
+ else myMailboxDir = cpystr (home);
+ }
+ return myMailboxDir ? myMailboxDir : "";
+}
+
+
+/* Return system standard INBOX
+ * Accepts: buffer string
+ */
+
+char *sysinbox ()
+{
+ char tmp[MAILTMPLEN];
+ if (!sysInbox) { /* initialize if first time */
+ sprintf (tmp,"%s/%s",MAILSPOOL,myusername ());
+ sysInbox = cpystr (tmp); /* system inbox is from mail spool */
+ }
+ return sysInbox;
+}
+
+/* Return mailbox directory name
+ * Accepts: destination buffer
+ * directory prefix
+ * name in directory
+ * Returns: file name or NIL if error
+ */
+
+char *mailboxdir (char *dst,char *dir,char *name)
+{
+ char tmp[MAILTMPLEN];
+ if (dir || name) { /* if either argument provided */
+ if (dir) {
+ if (strlen (dir) > NETMAXMBX) return NIL;
+ strcpy (tmp,dir); /* write directory prefix */
+ }
+ else tmp[0] = '\0'; /* otherwise null string */
+ if (name) {
+ if (strlen (name) > NETMAXMBX) return NIL;
+ strcat (tmp,name); /* write name in directory */
+ }
+ /* validate name, return its name */
+ if (!mailboxfile (dst,tmp)) return NIL;
+ }
+ /* no arguments, wants mailbox directory */
+ else strcpy (dst,mymailboxdir ());
+ return dst; /* return the name */
+}
+
+/* Return mailbox file name
+ * Accepts: destination buffer
+ * mailbox name
+ * Returns: file name or empty string for driver-selected INBOX or NIL if error
+ */
+
+char *mailboxfile (char *dst,char *name)
+{
+ struct passwd *pw;
+ char *s;
+ if (!name || !*name || (*name == '{') || (strlen (name) > NETMAXMBX) ||
+ ((anonymous || restrictBox || (*name == '#')) &&
+ (strstr (name,"..") || strstr (name,"//") || strstr (name,"/~"))))
+ dst = NIL; /* invalid name */
+ else switch (*name) { /* determine mailbox type based upon name */
+ case '#': /* namespace name */
+ /* #ftp/ namespace */
+ if (((name[1] == 'f') || (name[1] == 'F')) &&
+ ((name[2] == 't') || (name[2] == 'T')) &&
+ ((name[3] == 'p') || (name[3] == 'P')) &&
+ (name[4] == '/') && ftpHome) sprintf (dst,"%s/%s",ftpHome,name+5);
+ /* #public/ and #shared/ namespaces */
+ else if ((((name[1] == 'p') || (name[1] == 'P')) &&
+ ((name[2] == 'u') || (name[2] == 'U')) &&
+ ((name[3] == 'b') || (name[3] == 'B')) &&
+ ((name[4] == 'l') || (name[4] == 'L')) &&
+ ((name[5] == 'i') || (name[5] == 'I')) &&
+ ((name[6] == 'c') || (name[6] == 'C')) &&
+ (name[7] == '/') && (s = publicHome)) ||
+ (!anonymous && ((name[1] == 's') || (name[1] == 'S')) &&
+ ((name[2] == 'h') || (name[2] == 'H')) &&
+ ((name[3] == 'a') || (name[3] == 'A')) &&
+ ((name[4] == 'r') || (name[4] == 'R')) &&
+ ((name[5] == 'e') || (name[5] == 'E')) &&
+ ((name[6] == 'd') || (name[6] == 'D')) &&
+ (name[7] == '/') && (s = sharedHome)))
+ sprintf (dst,"%s/%s",s,compare_cstring (name,"INBOX") ? name : "INBOX");
+ else dst = NIL; /* unknown namespace */
+ break;
+
+ case '/': /* root access */
+ if (anonymous) dst = NIL; /* anonymous forbidden to do this */
+ else if ((restrictBox & RESTRICTROOT) && strcmp (name,sysinbox ()))
+ dst = NIL; /* restricted and not access to sysinbox */
+ else strcpy (dst,name); /* unrestricted, copy root name */
+ break;
+ case '~': /* other user access */
+ /* bad syntax or anonymous can't win */
+ if (!*++name || anonymous) dst = NIL;
+ /* ~/ equivalent to ordinary name */
+ else if (*name == '/') sprintf (dst,"%s/%s",mymailboxdir (),name+1);
+ /* other user forbidden if restricted */
+ else if (restrictBox & RESTRICTOTHERUSER) dst = NIL;
+ else { /* clear box other user */
+ /* copy user name */
+ for (s = dst; *name && (*name != '/'); *s++ = *name++);
+ *s++ = '\0'; /* tie off user name, look up in passwd file */
+ if ((pw = getpwnam (dst)) && pw->pw_dir) {
+ if (*name) name++; /* skip past the slash */
+ /* canonicalize case of INBOX */
+ if (!compare_cstring (name,"INBOX")) name = "INBOX";
+ /* remove trailing / from directory */
+ if ((s = strrchr (pw->pw_dir,'/')) && !s[1]) *s = '\0';
+ /* don't allow ~root/ if restricted root */
+ if ((restrictBox & RESTRICTROOT) && !*pw->pw_dir) dst = NIL;
+ /* build final name w/ subdir if needed */
+ else if (mailsubdir) sprintf (dst,"%s/%s/%s",pw->pw_dir,mailsubdir,name);
+ else sprintf (dst,"%s/%s",pw->pw_dir,name);
+ }
+ else dst = NIL; /* no such user */
+ }
+ break;
+ case 'I': case 'i': /* possible INBOX */
+ if (!compare_cstring (name+1,"NBOX")) {
+ /* if anonymous, use INBOX in mailbox dir */
+ if (anonymous) sprintf (dst,"%s/INBOX",mymailboxdir ());
+ else *dst = '\0'; /* otherwise driver selects the name */
+ break;
+ }
+ /* drop into to ordinary name case */
+ default: /* ordinary name is easy */
+ sprintf (dst,"%s/%s",mymailboxdir (),name);
+ break;
+ }
+ return dst; /* return final name */
+}
+
+/* Dot-lock file locker
+ * Accepts: file name to lock
+ * destination buffer for lock file name
+ * open file description on file name to lock
+ * Returns: T if success, NIL if failure
+ */
+
+long dotlock_lock (char *file,DOTLOCK *base,int fd)
+{
+ int i = locktimeout * 60;
+ int j,mask,retry,pi[2],po[2];
+ char *s,tmp[MAILTMPLEN];
+ struct stat sb;
+ /* flush absurd file name */
+ if (strlen (file) > 512) return NIL;
+ /* build lock filename */
+ sprintf (base->lock,"%s.lock",file);
+ /* assume no pipe */
+ base->pipei = base->pipeo = -1;
+ do { /* make sure not symlink */
+ if (!(j = chk_notsymlink (base->lock,&sb))) return NIL;
+ /* time out if file older than 5 minutes */
+ if ((j > 0) && ((time (0)) >= (sb.st_ctime + locktimeout * 60))) i = 0;
+ /* try to create the lock */
+ if ((j = open (name,O_WRONLY|O_CREAT|O_EXCL,(int) lock_protection)) >= 0) {
+ close (i); /* make the file, now close it */
+ chmod (base->lock,(int) lock_protection);
+ return LONGT;
+ }
+ if (errno == EEXIST) { /* already locked? */
+ retry = -1; /* can try again */
+ if (!(i%15)) { /* time to notify? */
+ sprintf (tmp,"Mailbox %.80s is locked, will override in %d seconds...",
+ file,i);
+ mm_log (tmp,WARN);
+ }
+ sleep (1); /* wait 1 second before next try */
+ }
+ else retry = i = 0; /* hard failure, no more retries */
+ } while (i--); /* until out of retries */
+ if (retry < 0) { /* still returning retry after locktimeout? */
+ if (!(j = chk_notsymlink (base->lock,&sb))) return NIL;
+ if ((j > 0) && ((time (0)) < (sb.st_ctime + locktimeout * 60))) {
+ sprintf (tmp,"Mailbox vulnerable - seizing %ld second old lock",
+ (long) (time (0) - sb.st_ctime));
+ mm_log (tmp,WARN);
+ }
+ mask = umask (0);
+ unlink (base->lock); /* try to remove the old file */
+ /* seize the lock */
+ if ((i = open (base->lock,O_WRONLY|O_CREAT,(int) lock_protection)) >= 0) {
+ close (i); /* don't need descriptor any more */
+ sprintf (tmp,"Mailbox %.80s lock overridden",file);
+ mm_log (tmp,NIL);
+ chmod (base->lock,(int) lock_protection);
+ umask (mask) /* restore old umask */
+ return LONGT;
+ }
+ umask (mask) /* restore old umask */
+ }
+
+ if (fd >= 0) switch (errno) {
+ case EACCES: /* protection failure? */
+ /* make command pipes */
+ if (!stat (LOCKPGM,&sb) && (pipe (pi) >= 0)) {
+ /* if input pipes usable create output pipes */
+ if ((pi[0] < FD_SETSIZE) && (pi[1] < FD_SETSIZE) && (pipe (po) >= 0)) {
+ /* make sure output pipes are usable */
+ if ((po[0] >= FD_SETSIZE) || (po[1] >= FD_SETSIZE));
+ /* all is good, make inferior process */
+ else if (!(j = fork ())) {
+ if (!fork ()) { /* make grandchild so it's inherited by init */
+ long cf; /* don't change caller vars in case vfork() */
+ char *argv[4],arg[20];
+ /* prepare argument vector */
+ sprintf (arg,"%d",fd);
+ argv[0] = LOCKPGM; argv[1] = arg;
+ argv[2] = file; argv[3] = NIL;
+ /* set parent's I/O to my O/I */
+ dup2 (pi[1],1); dup2 (pi[1],2); dup2 (po[0],0);
+ /* close all unnecessary descriptors */
+ for (cf = max (20,max (max (pi[0],pi[1]),max(po[0],po[1])));
+ cf >= 3; --cf) if (cf != fd) close (cf);
+ /* be our own process group */
+ setpgrp (0,getpid ());
+ /* now run it */
+ _exit (execv (argv[0],argv));
+ }
+ _exit (1); /* child is done */
+ }
+ else if (j > 0) { /* parent process */
+ fd_set rfd;
+ struct timeval tmo;
+ FD_ZERO (&rfd);
+ FD_SET (pi[0],&rfd);
+ tmo.tv_sec = locktimeout * 60;
+ grim_pid_reap (j,NIL);/* reap child; grandchild now owned by init */
+ /* read response from locking program */
+ if (select (pi[0]+1,&rfd,0,0,&tmo) &&
+ (read (pi[0],tmp,1) == 1) && (tmp[0] == '+')) {
+ /* success, record pipes */
+ base->pipei = pi[0]; base->pipeo = po[1];
+ /* close child's side of the pipes */
+ close (pi[1]); close (po[0]);
+ return LONGT;
+ }
+ }
+ close (po[0]); close (po[1]);
+ }
+ close (pi[0]); close (pi[1]);
+ }
+ /* find directory/file delimiter */
+ if (s = strrchr (base->lock,'/')) {
+ *s = '\0'; /* tie off at directory */
+ sprintf(tmp, /* generate default message */
+ "Mailbox vulnerable - directory %.80s must have 1777 protection",
+ base->lock);
+ /* definitely not 1777 if can't stat */
+ mask = stat (base->lock,&sb) ? 0 : (sb.st_mode & 1777);
+ *s = '/'; /* restore lock name */
+ if (mask != 1777) { /* default warning if not 1777 */
+ MM_LOG (tmp,WARN);
+ break;
+ }
+ }
+ default:
+ sprintf (tmp,"Mailbox vulnerable - error creating %.80s: %s",
+ base->lock,strerror (errno));
+ mm_log (tmp,WARN); /* this is probably not good */
+ break;
+ }
+ base->lock[0] = '\0'; /* don't use lock files */
+ return NIL;
+}
+
+/* Dot-lock file unlocker
+ * Accepts: lock file name
+ * Returns: T if success, NIL if failure
+ */
+
+long dotlock_unlock (DOTLOCK *base)
+{
+ long ret = LONGT;
+ if (base && base->lock[0]) {
+ if (base->pipei >= 0) { /* if running through a pipe unlocker */
+ ret = (write (base->pipeo,"+",1) == 1);
+ /* nuke the pipes */
+ close (base->pipei); close (base->pipeo);
+ }
+ else ret = !unlink (base->lock);
+ }
+ return ret;
+}
+
+/* Lock file name
+ * Accepts: scratch buffer
+ * file name
+ * type of locking operation (LOCK_SH or LOCK_EX)
+ * pointer to return PID of locker
+ * Returns: file descriptor of lock or negative if error
+ */
+
+int lockname (char *lock,char *fname,int op,long *pid)
+{
+ struct stat sbuf;
+ *pid = 0; /* no locker PID */
+ return stat (fname,&sbuf) ? -1 : lock_work (lock,&sbuf,op,pid);
+}
+
+
+/* Lock file descriptor
+ * Accepts: file descriptor
+ * lock file name buffer
+ * type of locking operation (LOCK_SH or LOCK_EX)
+ * Returns: file descriptor of lock or negative if error
+ */
+
+int lockfd (int fd,char *lock,int op)
+{
+ struct stat sbuf;
+ return fstat (fd,&sbuf) ? -1 : lock_work (lock,&sbuf,op,NIL);
+}
+
+/* Lock file name worker
+ * Accepts: lock file name
+ * pointer to stat() buffer
+ * type of locking operation (LOCK_SH or LOCK_EX)
+ * pointer to return PID of locker
+ * Returns: file descriptor of lock or negative if error
+ */
+
+int lock_work (char *lock,void *sb,int op,long *pid)
+{
+ struct stat lsb,fsb;
+ struct stat *sbuf = (struct stat *) sb;
+ char tmp[MAILTMPLEN];
+ long i;
+ int fd;
+ int mask = umask (0);
+ if (pid) *pid = 0; /* initialize return PID */
+ /* make temporary lock file name */
+ sprintf (lock,"%s/.%lx.%lx","/tmp",
+ (unsigned long) sbuf->st_dev,(unsigned long) sbuf->st_ino);
+ while (T) { /* until get a good lock */
+ do switch ((int) chk_notsymlink (lock,&lsb)) {
+ case 1: /* exists just once */
+ if (((fd = open (lock,O_RDWR,lock_protection)) >= 0) ||
+ (errno != ENOENT) || (chk_notsymlink (lock,&lsb) >= 0)) break;
+ case -1: /* name doesn't exist */
+ fd = open (lock,O_RDWR|O_CREAT|O_EXCL,lock_protection);
+ break;
+ default: /* multiple hard links */
+ mm_log ("hard link to lock name",ERROR);
+ syslog (LOG_CRIT,"SECURITY PROBLEM: hard link to lock name: %.80s",lock);
+ case 0: /* symlink (already did syslog) */
+ umask (mask); /* restore old mask */
+ return -1; /* fail: no lock file */
+ } while ((fd < 0) && (errno == EEXIST));
+ if (fd < 0) { /* failed to get file descriptor */
+ syslog (LOG_INFO,"Mailbox lock file %s open failure: %s",lock,
+ strerror (errno));
+ if (stat ("/tmp",&lsb))
+ syslog (LOG_CRIT,"SYSTEM ERROR: no /tmp: %s",strerror (errno));
+ else if ((lsb.st_mode & 01777) != 01777)
+ mm_log ("Can't lock for write: /tmp must have 1777 protection",WARN);
+ umask (mask); /* restore old mask */
+ return -1; /* fail: can't open lock file */
+ }
+
+ /* non-blocking form */
+ if (op & LOCK_NB) i = flock (fd,op);
+ else { /* blocking form */
+ (*mailblocknotify) (BLOCK_FILELOCK,NIL);
+ i = flock (fd,op);
+ (*mailblocknotify) (BLOCK_NONE,NIL);
+ }
+ if (i) { /* failed, get other process' PID */
+ if (pid && !fstat (fd,&fsb) && (i = min (fsb.st_size,MAILTMPLEN-1)) &&
+ (read (fd,tmp,i) == i) && !(tmp[i] = 0) && ((i = atol (tmp)) > 0))
+ *pid = i;
+ close (fd); /* failed, give up on lock */
+ umask (mask); /* restore old mask */
+ return -1; /* fail: can't lock */
+ }
+ /* make sure this lock is good for us */
+ if (!lstat (lock,&lsb) && ((lsb.st_mode & S_IFMT) != S_IFLNK) &&
+ !fstat (fd,&fsb) && (lsb.st_dev == fsb.st_dev) &&
+ (lsb.st_ino == fsb.st_ino) && (fsb.st_nlink == 1)) break;
+ close (fd); /* lock not right, drop fd and try again */
+ }
+ /* make sure mode OK (don't use fchmod()) */
+ chmod (lock,(int) lock_protection);
+ umask (mask); /* restore old mask */
+ return fd; /* success */
+}
+
+/* Check to make sure not a symlink
+ * Accepts: file name
+ * stat buffer
+ * Returns: -1 if doesn't exist, NIL if symlink, else number of hard links
+ */
+
+long chk_notsymlink (char *name,void *sb)
+{
+ struct stat *sbuf = (struct stat *) sb;
+ /* name exists? */
+ if (lstat (name,sbuf)) return -1;
+ /* forbid symbolic link */
+ if ((sbuf->st_mode & S_IFMT) == S_IFLNK) {
+ mm_log ("symbolic link on lock name",ERROR);
+ syslog (LOG_CRIT,"SECURITY PROBLEM: symbolic link on lock name: %.80s",
+ name);
+ return NIL;
+ }
+ return (long) sbuf->st_nlink; /* return number of hard links */
+}
+
+
+/* Unlock file descriptor
+ * Accepts: file descriptor
+ * lock file name from lockfd()
+ */
+
+void unlockfd (int fd,char *lock)
+{
+ /* delete the file if no sharers */
+ if (!flock (fd,LOCK_EX|LOCK_NB)) unlink (lock);
+ flock (fd,LOCK_UN); /* unlock it */
+ close (fd); /* close it */
+}
+
+/* Set proper file protection for mailbox
+ * Accepts: mailbox name
+ * actual file path name
+ * Returns: T, always
+ */
+
+long set_mbx_protections (char *mailbox,char *path)
+{
+ struct stat sbuf;
+ int mode = (int) mbx_protection;
+ if (*mailbox == '#') { /* possible namespace? */
+ if (((mailbox[1] == 'f') || (mailbox[1] == 'F')) &&
+ ((mailbox[2] == 't') || (mailbox[2] == 'T')) &&
+ ((mailbox[3] == 'p') || (mailbox[3] == 'P')) &&
+ (mailbox[4] == '/')) mode = (int) ftp_protection;
+ else if (((mailbox[1] == 'p') || (mailbox[1] == 'P')) &&
+ ((mailbox[2] == 'u') || (mailbox[2] == 'U')) &&
+ ((mailbox[3] == 'b') || (mailbox[3] == 'B')) &&
+ ((mailbox[4] == 'l') || (mailbox[4] == 'L')) &&
+ ((mailbox[5] == 'i') || (mailbox[5] == 'I')) &&
+ ((mailbox[6] == 'c') || (mailbox[6] == 'C')) &&
+ (mailbox[7] == '/')) mode = (int) public_protection;
+ else if (((mailbox[1] == 's') || (mailbox[1] == 'S')) &&
+ ((mailbox[2] == 'h') || (mailbox[2] == 'H')) &&
+ ((mailbox[3] == 'a') || (mailbox[3] == 'A')) &&
+ ((mailbox[4] == 'r') || (mailbox[4] == 'R')) &&
+ ((mailbox[5] == 'e') || (mailbox[5] == 'E')) &&
+ ((mailbox[6] == 'd') || (mailbox[6] == 'D')) &&
+ (mailbox[7] == '/')) mode = (int) shared_protection;
+ }
+ /* if a directory */
+ if (!stat (path,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
+ /* set owner search if allow read or write */
+ if (mode & 0600) mode |= 0100;
+ if (mode & 060) mode |= 010;/* set group search if allow read or write */
+ if (mode & 06) mode |= 01; /* set world search if allow read or write */
+ /* preserve directory SGID bit */
+ if (sbuf.st_mode & S_ISGID) mode |= S_ISGID;
+ }
+ chmod (path,mode); /* set the new protection, ignore failure */
+ return LONGT;
+}
+
+/* Get proper directory protection
+ * Accepts: mailbox name
+ * Returns: directory mode, always
+ */
+
+long get_dir_protection (char *mailbox)
+{
+ if (*mailbox == '#') { /* possible namespace? */
+ if (((mailbox[1] == 'f') || (mailbox[1] == 'F')) &&
+ ((mailbox[2] == 't') || (mailbox[2] == 'T')) &&
+ ((mailbox[3] == 'p') || (mailbox[3] == 'P')) &&
+ (mailbox[4] == '/')) return ftp_dir_protection;
+ else if (((mailbox[1] == 'p') || (mailbox[1] == 'P')) &&
+ ((mailbox[2] == 'u') || (mailbox[2] == 'U')) &&
+ ((mailbox[3] == 'b') || (mailbox[3] == 'B')) &&
+ ((mailbox[4] == 'l') || (mailbox[4] == 'L')) &&
+ ((mailbox[5] == 'i') || (mailbox[5] == 'I')) &&
+ ((mailbox[6] == 'c') || (mailbox[6] == 'C')) &&
+ (mailbox[7] == '/')) return public_dir_protection;
+ else if (((mailbox[1] == 's') || (mailbox[1] == 'S')) &&
+ ((mailbox[2] == 'h') || (mailbox[2] == 'H')) &&
+ ((mailbox[3] == 'a') || (mailbox[3] == 'A')) &&
+ ((mailbox[4] == 'r') || (mailbox[4] == 'R')) &&
+ ((mailbox[5] == 'e') || (mailbox[5] == 'E')) &&
+ ((mailbox[6] == 'd') || (mailbox[6] == 'D')) &&
+ (mailbox[7] == '/')) return shared_dir_protection;
+ }
+ return dir_protection;
+}
+
+/* Determine default prototype stream to user
+ * Accepts: type (NIL for create, T for append)
+ * Returns: default prototype stream
+ */
+
+MAILSTREAM *default_proto (long type)
+{
+ myusername (); /* make sure initialized */
+ /* return default driver's prototype */
+ return type ? appendProto : createProto;
+}
+
+
+/* Set up user flags for stream
+ * Accepts: MAIL stream
+ * Returns: MAIL stream with user flags set up
+ */
+
+MAILSTREAM *user_flags (MAILSTREAM *stream)
+{
+ int i;
+ myusername (); /* make sure initialized */
+ for (i = 0; i < NUSERFLAGS && userFlags[i]; ++i)
+ if (!stream->user_flags[i]) stream->user_flags[i] = cpystr (userFlags[i]);
+ return stream;
+}
+
+
+/* Return nth user flag
+ * Accepts: user flag number
+ * Returns: flag
+ */
+
+char *default_user_flag (unsigned long i)
+{
+ myusername (); /* make sure initialized */
+ return userFlags[i];
+}
+
+/* Default block notify routine
+ * Accepts: reason for calling
+ * data
+ * Returns: data
+ */
+
+void *mm_blocknotify (int reason,void *data)
+{
+ void *ret = data;
+ switch (reason) {
+ case BLOCK_SENSITIVE: /* entering sensitive code */
+ ret = (void *) alarm (0);
+ break;
+ case BLOCK_NONSENSITIVE: /* exiting sensitive code */
+ if ((unsigned int) data) alarm ((unsigned int) data);
+ break;
+ default: /* ignore all other reasons */
+ break;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/amiga/env_ami.h b/imap/src/osdep/amiga/env_ami.h
new file mode 100644
index 00000000..365e5128
--- /dev/null
+++ b/imap/src/osdep/amiga/env_ami.h
@@ -0,0 +1,95 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+typedef struct dotlock_base {
+ char lock[MAILTMPLEN];
+ int pipei;
+ int pipeo;
+} DOTLOCK;
+
+
+/* Bits that can be set in restrictBox */
+
+#define RESTRICTROOT 0x1 /* restricted box doesn't allow root */
+#define RESTRICTOTHERUSER 0x2 /* restricted box doesn't allow other user */
+
+/* Subscription definitions for UNIX */
+
+#define SUBSCRIPTIONFILE(t) sprintf (t,"%s/.mailboxlist",myhomedir ())
+#define SUBSCRIPTIONTEMP(t) sprintf (t,"%s/.mlbxlsttmp",myhomedir ())
+
+
+/* dorc() options */
+
+#define SYSCONFIG "/etc/c-client.cf"
+
+
+/* Special users */
+
+#define ANONYMOUSUSER "nobody" /* anonymous user */
+#define UNLOGGEDUSER "root" /* unlogged-in user */
+#define ADMINGROUP "mailadm" /* mail administrator group */
+
+/* Function prototypes */
+
+#include "env.h"
+
+void rfc822_fixed_date (char *date);
+long env_init (char *user,char *home);
+char *myusername_full (unsigned long *flags);
+#define MU_LOGGEDIN 0
+#define MU_NOTLOGGEDIN 1
+#define MU_ANONYMOUS 2
+#define myusername() \
+ myusername_full (NIL)
+char *sysinbox ();
+char *mailboxdir (char *dst,char *dir,char *name);
+long dotlock_lock (char *file,DOTLOCK *base,int fd);
+long dotlock_unlock (DOTLOCK *base);
+int lockname (char *lock,char *fname,int op,long *pid);
+int lockfd (int fd,char *lock,int op);
+int lock_work (char *lock,void *sbuf,int op,long *pid);
+long chk_notsymlink (char *name,void *sbuf);
+void unlockfd (int fd,char *lock);
+long set_mbx_protections (char *mailbox,char *path);
+long get_dir_protection (char *mailbox);
+MAILSTREAM *user_flags (MAILSTREAM *stream);
+char *default_user_flag (unsigned long i);
+void dorc (char *file,long flag);
+long path_create (MAILSTREAM *stream,char *mailbox);
+void grim_pid_reap_status (int pid,int killreq,void *status);
+#define grim_pid_reap(pid,killreq) \
+ grim_pid_reap_status (pid,killreq,NIL)
+long safe_write (int fd,char *buf,long nbytes);
+void *arm_signal (int sig,void *action);
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[]);
+long loginpw (struct passwd *pw,int argc,char *argv[]);
+long pw_login (struct passwd *pw,char *auser,char *user,char *home,int argc,
+ char *argv[]);
+void *mm_blocknotify (int reason,void *data);
diff --git a/imap/src/osdep/amiga/fdstring.c b/imap/src/osdep/amiga/fdstring.c
new file mode 100644
index 00000000..7a491f7d
--- /dev/null
+++ b/imap/src/osdep/amiga/fdstring.c
@@ -0,0 +1,99 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: File descriptor string routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 April 1997
+ * Last Edited: 4 April 2007
+ */
+
+#include "mail.h"
+#include "osdep.h"
+#include "misc.h"
+#include "fdstring.h"
+
+/* String driver for fd stringstructs */
+
+static void fd_string_init (STRING *s,void *data,unsigned long size);
+static char fd_string_next (STRING *s);
+static void fd_string_setpos (STRING *s,unsigned long i);
+
+STRINGDRIVER fd_string = {
+ fd_string_init, /* initialize string structure */
+ fd_string_next, /* get next byte in string structure */
+ fd_string_setpos /* set position in string structure */
+};
+
+
+/* Initialize string structure for fd stringstruct
+ * Accepts: string structure
+ * pointer to string
+ * size of string
+ */
+
+static void fd_string_init (STRING *s,void *data,unsigned long size)
+{
+ FDDATA *d = (FDDATA *) data;
+ /* note fd */
+ s->data = (void *) (unsigned long) d->fd;
+ s->data1 = d->pos; /* note file offset */
+ s->size = size; /* note size */
+ s->curpos = s->chunk = d->chunk;
+ s->chunksize = (unsigned long) d->chunksize;
+ s->offset = 0; /* initial position */
+ /* and size of data */
+ s->cursize = min (s->chunksize,size);
+ /* move to that position in the file */
+ lseek (d->fd,d->pos,L_SET);
+ read (d->fd,s->chunk,(size_t) s->cursize);
+}
+
+/* Get next character from fd stringstruct
+ * Accepts: string structure
+ * Returns: character, string structure chunk refreshed
+ */
+
+static char fd_string_next (STRING *s)
+{
+ char c = *s->curpos++; /* get next byte */
+ SETPOS (s,GETPOS (s)); /* move to next chunk */
+ return c; /* return the byte */
+}
+
+
+/* Set string pointer position for fd stringstruct
+ * Accepts: string structure
+ * new position
+ */
+
+static void fd_string_setpos (STRING *s,unsigned long i)
+{
+ if (i > s->size) i = s->size; /* don't permit setting beyond EOF */
+ s->offset = i; /* set new offset */
+ s->curpos = s->chunk; /* reset position */
+ /* set size of data */
+ if (s->cursize = min (s->chunksize,SIZE (s))) {
+ /* move to that position in the file */
+ lseek ((long) s->data,s->data1 + s->offset,L_SET);
+ read ((long) s->data,s->curpos,(size_t) s->cursize);
+ }
+}
diff --git a/imap/src/osdep/amiga/fdstring.h b/imap/src/osdep/amiga/fdstring.h
new file mode 100644
index 00000000..d0a021bf
--- /dev/null
+++ b/imap/src/osdep/amiga/fdstring.h
@@ -0,0 +1,39 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: File descriptor string routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 April 1997
+ * Last Edited: 30 August 2006
+ */
+
+/* Driver-dependent data passed to init method */
+
+typedef struct fd_data {
+ int fd; /* file descriptor */
+ unsigned long pos; /* initial position */
+ char *chunk; /* I/O buffer chunk */
+ unsigned long chunksize; /* I/O buffer chunk length */
+} FDDATA;
+
+
+extern STRINGDRIVER fd_string;
diff --git a/imap/src/osdep/amiga/fs_ami.c b/imap/src/osdep/amiga/fs_ami.c
new file mode 100644
index 00000000..b433e1b4
--- /dev/null
+++ b/imap/src/osdep/amiga/fs_ami.c
@@ -0,0 +1,71 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Free storage management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Get a block of free storage
+ * Accepts: size of desired block
+ * Returns: free storage block
+ */
+
+void *fs_get (size_t size)
+{
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ void *data = (*bn) (BLOCK_SENSITIVE,NIL);
+ void *block = malloc (size ? size : (size_t) 1);
+ if (!block) fatal ("Out of memory");
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ return (block);
+}
+
+
+/* Resize a block of free storage
+ * Accepts: ** pointer to current block
+ * new size
+ */
+
+void fs_resize (void **block,size_t size)
+{
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ void *data = (*bn) (BLOCK_SENSITIVE,NIL);
+ if (!(*block = realloc (*block,size ? size : (size_t) 1)))
+ fatal ("Can't resize memory");
+ (*bn) (BLOCK_NONSENSITIVE,data);
+}
+
+
+/* Return a block of free storage
+ * Accepts: ** pointer to free storage block
+ */
+
+void fs_give (void **block)
+{
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ void *data = (*bn) (BLOCK_SENSITIVE,NIL);
+ free (*block);
+ *block = NIL;
+ (*bn) (BLOCK_NONSENSITIVE,data);
+}
diff --git a/imap/src/osdep/amiga/ftl_ami.c b/imap/src/osdep/amiga/ftl_ami.c
new file mode 100644
index 00000000..9e65ef55
--- /dev/null
+++ b/imap/src/osdep/amiga/ftl_ami.c
@@ -0,0 +1,38 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: DOS/VMS/TOPS-20 crash management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Report a fatal error
+ * Accepts: string to output
+ */
+
+void fatal (char *string)
+{
+ mm_fatal (string); /* pass up the string */
+ abort (); /* die horribly */
+}
diff --git a/imap/src/osdep/amiga/gethstid.c b/imap/src/osdep/amiga/gethstid.c
new file mode 100644
index 00000000..01516976
--- /dev/null
+++ b/imap/src/osdep/amiga/gethstid.c
@@ -0,0 +1,38 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Get host ID emulator
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 3 May 1995
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Emulator for BSD gethostid() call
+ * Returns: unique identifier for this machine
+ */
+
+long gethostid (void)
+{
+ /* No gethostid() here, so just fake it and hope things turn out okay. */
+ return 0xdeadface;
+}
diff --git a/imap/src/osdep/amiga/gr_waitp.c b/imap/src/osdep/amiga/gr_waitp.c
new file mode 100644
index 00000000..8b230f9d
--- /dev/null
+++ b/imap/src/osdep/amiga/gr_waitp.c
@@ -0,0 +1,39 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX Grim PID Reaper -- waitpid() version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 30 November 1993
+ * Last Edited: 30 August 2006
+ */
+
+/* Grim PID reaper
+ * Accepts: process ID
+ * kill request flag
+ * status return value
+ */
+
+void grim_pid_reap_status (int pid,int killreq,void *status)
+{
+ if (killreq) kill(pid,SIGHUP);/* kill if not already dead */
+ while ((waitpid (pid,status,NIL) < 0) && (errno != ECHILD));
+}
diff --git a/imap/src/osdep/amiga/log_std.c b/imap/src/osdep/amiga/log_std.c
new file mode 100644
index 00000000..a36fa88f
--- /dev/null
+++ b/imap/src/osdep/amiga/log_std.c
@@ -0,0 +1,44 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Standard login
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Log in
+ * Accepts: login passwd struct
+ * argument count
+ * argument vector
+ * Returns: T if success, NIL otherwise
+ */
+
+long loginpw (struct passwd *pw,int argc,char *argv[])
+{
+ uid_t uid = pw->pw_uid;
+ char *name = cpystr (pw->pw_name);
+ long ret = !(setgid (pw->pw_gid) || initgroups (name,pw->pw_gid) ||
+ setuid (uid));
+ fs_give ((void **) &name);
+ return ret;
+}
diff --git a/imap/src/osdep/amiga/mbx.c b/imap/src/osdep/amiga/mbx.c
new file mode 100644
index 00000000..1ece5d8d
--- /dev/null
+++ b/imap/src/osdep/amiga/mbx.c
@@ -0,0 +1,1855 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MBX mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 3 October 1995
+ * Last Edited: 11 October 2007
+ */
+
+
+/* FILE TIME SEMANTICS
+ *
+ * The atime is the last read time of the file.
+ * The mtime is the last flags update time of the file.
+ * The ctime is the last write time of the file.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+
+/* Build parameters */
+
+#define HDRSIZE 2048
+
+
+/* Kludge to make Cygwin happy */
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+/* MBX I/O stream local data */
+
+typedef struct mbx_local {
+ unsigned int flagcheck: 1; /* if ping should sweep for flags */
+ unsigned int expok: 1; /* if expunging OK in ping */
+ unsigned int expunged : 1; /* if one or more expunged messages */
+ int fd; /* file descriptor for I/O */
+ int ld; /* lock file descriptor */
+ int ffuserflag; /* first free user flag */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* last snarf time */
+ unsigned long lastpid; /* PID of last writer */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ char lock[MAILTMPLEN]; /* buffer to write lock name */
+} MBXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MBXLOCAL *) stream->local)
+
+/* Function prototypes */
+
+DRIVER *mbx_valid (char *name);
+int mbx_isvalid (MAILSTREAM **stream,char *name,char *tmp,int *ld,char *lock,
+ long flags);
+void *mbx_parameters (long function,void *value);
+void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void mbx_list (MAILSTREAM *stream,char *ref,char *pat);
+void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mbx_create (MAILSTREAM *stream,char *mailbox);
+long mbx_delete (MAILSTREAM *stream,char *mailbox);
+long mbx_rename (MAILSTREAM *stream,char *old,char *newname);
+long mbx_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *mbx_open (MAILSTREAM *stream);
+void mbx_close (MAILSTREAM *stream,long options);
+void mbx_abort (MAILSTREAM *stream);
+void mbx_flags (MAILSTREAM *stream,char *sequence,long flags);
+char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags);
+long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long mbx_ping (MAILSTREAM *stream);
+void mbx_check (MAILSTREAM *stream);
+long mbx_expunge (MAILSTREAM *stream,char *sequence,long options);
+void mbx_snarf (MAILSTREAM *stream);
+long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+char *mbx_file (char *dst,char *name);
+long mbx_parse (MAILSTREAM *stream);
+MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok);
+unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
+void mbx_update_header (MAILSTREAM *stream);
+void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags);
+unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size,char **hdr);
+unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed,
+ long flags);
+long mbx_flaglock (MAILSTREAM *stream);
+
+/* MBX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mbxdriver = {
+ "mbx", /* driver name */
+ DR_LOCAL|DR_MAIL|DR_CRLF|DR_LOCKING,
+ /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ mbx_valid, /* mailbox is valid for us */
+ mbx_parameters, /* manipulate parameters */
+ mbx_scan, /* scan mailboxes */
+ mbx_list, /* list mailboxes */
+ mbx_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ mbx_create, /* create mailbox */
+ mbx_delete, /* delete mailbox */
+ mbx_rename, /* rename mailbox */
+ mbx_status, /* status of mailbox */
+ mbx_open, /* open mailbox */
+ mbx_close, /* close mailbox */
+ mbx_flags, /* fetch message "fast" attributes */
+ mbx_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mbx_header, /* fetch message header */
+ mbx_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ mbx_flag, /* modify flags */
+ mbx_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mbx_ping, /* ping mailbox to see if still alive */
+ mbx_check, /* check for new messages */
+ mbx_expunge, /* expunge deleted messages */
+ mbx_copy, /* copy messages to another mailbox */
+ mbx_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mbxproto = {&mbxdriver};
+
+/* MBX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mbx_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ int fd = mbx_isvalid (NIL,name,tmp,NIL,NIL,NIL);
+ if (fd < 0) return NIL;
+ close (fd); /* don't need the fd now */
+ return &mbxdriver;
+}
+
+
+/* MBX mail test for valid mailbox
+ * Accepts: returned stream with valid mailbox keywords
+ * mailbox name
+ * scratch buffer
+ * returned lock fd
+ * returned lock name
+ * RW flags or NIL for readonly
+ * Returns: file descriptor if valid, NIL otherwise
+ */
+
+#define MBXISVALIDNOUID 0x1 /* RW, don't do UID action */
+#define MBXISVALIDUID 0x2 /* RW, do UID action */
+
+int mbx_isvalid (MAILSTREAM **stream,char *name,char *tmp,int *ld,char *lock,
+ long flags)
+{
+ int fd,upd;
+ int ret = -1;
+ unsigned long i;
+ long j,k;
+ off_t pos;
+ char c,*s,*t,hdr[HDRSIZE];
+ struct stat sbuf;
+ time_t tp[2];
+ int error = EINVAL; /* assume invalid argument */
+ if (ld) *ld = -1; /* initially no lock */
+ if ((s = mbx_file (tmp,name)) && !stat (s,&sbuf) &&
+ ((fd = open (tmp,(flags ? O_RDWR : O_RDONLY)|O_BINARY,NIL)) >= 0)) {
+ error = -1; /* bogus format */
+ /* I love cretinous C compilers -- don't you? */
+ if (read (fd,hdr,HDRSIZE) == HDRSIZE)
+ if ((hdr[0] == '*') && (hdr[1] == 'm') && (hdr[2] == 'b') &&
+ (hdr[3] == 'x') && (hdr[4] == '*') && (hdr[5] == '\015') &&
+ (hdr[6] == '\012') && isxdigit (hdr[7]) && isxdigit (hdr[8]))
+ if (isxdigit (hdr[9]) && isxdigit (hdr[10]) && isxdigit (hdr[11]) &&
+ isxdigit (hdr[12]) && isxdigit (hdr[13]) && isxdigit (hdr[14]) &&
+ isxdigit (c = hdr[15]) && isxdigit (hdr[16]))
+ if (isxdigit (hdr[17]) && isxdigit (hdr[18]) &&
+ isxdigit (hdr[19]) && isxdigit (hdr[20]) &&
+ isxdigit (hdr[21]) && isxdigit (hdr[22]) &&
+ (hdr[23] == '\015') && (hdr[24] == '\012')) {
+ ret = fd; /* mbx format */
+
+ if (stream) { /* lock if making mini-stream */
+ if (flock (fd,LOCK_SH) ||
+ (flags && ((*ld = lockfd (fd,lock,LOCK_EX)) < 0))) ret = -1;
+ /* reread data now that locked */
+ else if (lseek (fd,0,L_SET) ||
+ (read (fd,hdr,HDRSIZE) != HDRSIZE)) ret = -1;
+ else {
+ *stream = (MAILSTREAM *) memset (fs_get (sizeof (MAILSTREAM)),
+ 0,sizeof (MAILSTREAM));
+ hdr[15] = '\0'; /* tie off UIDVALIDITY */
+ (*stream)->uid_validity = strtoul (hdr+7,NIL,16);
+ hdr[15] = c; /* now get UIDLAST */
+ (*stream)->uid_last = strtoul (hdr+15,NIL,16);
+ /* parse user flags */
+ for (i = 0, s = hdr + 25;
+ (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s);
+ i++, s = t + 2) {
+ *t = '\0'; /* tie off flag */
+ if (strlen (s) <= MAXUSERFLAG)
+ (*stream)->user_flags[i] = cpystr (s);
+ }
+ /* make sure have true UIDLAST */
+ if (flags & MBXISVALIDUID) {
+ for (upd = NIL,pos = 2048, k = 0; pos < sbuf.st_size;
+ pos += (j + k)) {
+ /* read header for this message */
+ lseek (fd,pos,L_SET);
+ if ((j = read (fd,hdr,64)) >= 0) {
+ hdr[j] = '\0';
+ if ((s = strchr (hdr,'\015')) && (s[1] == '\012')) {
+ *s = '\0';
+ k = s + 2 - hdr;
+ if ((s = strchr (hdr,',')) &&
+ (j = strtol (s+1,&s,10)) && (*s == ';') &&
+ (s = strchr (s+1,'-'))) {
+ /* get UID if there is any */
+ i = strtoul (++s,&t,16);
+ if (!*t && (t == (s + 8)) &&
+ (i <= (*stream)->uid_last)) {
+ if (!i) {
+ lseek (fd,pos + s - hdr,L_SET);
+ sprintf (hdr,"%08lx",++(*stream)->uid_last);
+ write (fd,hdr,8);
+ upd = T;
+ }
+ continue;
+ }
+ }
+ }
+ ret = -1; /* error, give up */
+ *stream = mail_close (*stream);
+ pos = sbuf.st_size + 1;
+ j = k = 0;
+ }
+ }
+
+ if (upd) { /* need to update hdr with new UIDLAST? */
+ lseek (fd,15,L_SET);
+ sprintf (hdr,"%08lx",(*stream)->uid_last);
+ write (fd,hdr,8);
+ }
+ }
+ }
+ }
+ }
+ if (ret != fd) close (fd); /* close the file */
+ else lseek (fd,0,L_SET); /* else rewind to start */
+ /* \Marked status? */
+ if (sbuf.st_ctime > sbuf.st_atime) {
+ tp[0] = sbuf.st_atime; /* preserve atime and mtime */
+ tp[1] = sbuf.st_mtime;
+ utime (tmp,tp); /* set the times */
+ }
+ }
+ /* in case INBOX but not mbx format */
+ else if (((error = errno) == ENOENT) && !compare_cstring (name,"INBOX"))
+ error = -1;
+ if ((ret < 0) && ld && (*ld >= 0)) {
+ unlockfd (*ld,lock);
+ *ld = -1;
+ }
+ errno = error; /* return as last error */
+ return ret; /* return what we should */
+}
+
+/* MBX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mbx_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = mbx_file ((char *) value,"INBOX");
+ break;
+ case SET_ONETIMEEXPUNGEATPING:
+ if (value) ((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T;
+ case GET_ONETIMEEXPUNGEATPING:
+ if (value) ret = (void *)
+ (((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL);
+ break;
+ }
+ return ret;
+}
+
+
+/* MBX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MBX mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mbx_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MBX mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MBX mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,*t,mbx[MAILTMPLEN],tmp[HDRSIZE];
+ long ret = NIL;
+ int i,fd;
+ if (!(s = mbx_file (mbx,mailbox))) {
+ sprintf (mbx,"Can't create %.80s: invalid name",mailbox);
+ MM_LOG (mbx,ERROR);
+ }
+ /* create underlying file */
+ else if (dummy_create_path (stream,s,get_dir_protection (mailbox))) {
+ /* done if made directory */
+ if ((s = strrchr (s,'/')) && !s[1]) return T;
+ if ((fd = open (mbx,O_WRONLY|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else {
+ memset (tmp,'\0',HDRSIZE);/* initialize header */
+ sprintf (s = tmp,"*mbx*\015\012%08lx00000000\015\012",
+ (unsigned long) time (0));
+ for (i = 0; i < NUSERFLAGS; ++i) {
+ t = (stream && stream->user_flags[i]) ? stream->user_flags[i] :
+ ((t = default_user_flag (i)) ? t : "");
+ sprintf (s += strlen (s),"%s\015\012",t);
+ }
+ if (write (fd,tmp,HDRSIZE) != HDRSIZE) {
+ sprintf (tmp,"Can't initialize mailbox node %.80s: %s",
+ mbx,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else ret = T; /* success */
+ close (fd); /* close file */
+ }
+ }
+ /* set proper protections */
+ return ret ? set_mbx_protections (mailbox,mbx) : NIL;
+}
+
+
+/* MBX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return mbx_rename (stream,mailbox,NIL);
+}
+
+/* MBX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = LONGT;
+ char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ if (!mbx_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'/')) && !s[1])))) {
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else if ((fd = open (file,O_RDWR|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get parse/append permission */
+ if ((ld = lockfd (fd,lock,LOCK_EX)) < 0) {
+ MM_LOG ("Unable to lock rename mailbox",ERROR);
+ return NIL;
+ }
+ /* lock out other users */
+ if (flock (fd,LOCK_EX|LOCK_NB)) {
+ close (fd); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ MM_LOG (tmp,ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return NIL;
+ }
+
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if (s = strrchr (tmp,'/')) {
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* superior name doesn't exist, create it */
+ if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp,get_dir_protection (newname)))
+ ret = NIL;
+ else *s = c; /* restore full name */
+ }
+ /* rename the file */
+ if (ret && rename (file,tmp)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ else if (unlink (file)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ flock (fd,LOCK_UN); /* release lock on the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ close (fd); /* close the file */
+ /* recreate file if renamed INBOX */
+ if (ret && !compare_cstring (old,"INBOX")) mbx_create (NIL,"INBOX");
+ return ret; /* return success */
+}
+
+/* MBX Mail status
+ * Accepts: mail stream
+ * mailbox name
+ * status flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_status (MAILSTREAM *stream,char *mbx,long flags)
+{
+ MAILSTATUS status;
+ unsigned long i;
+ MAILSTREAM *tstream = NIL;
+ MAILSTREAM *systream = NIL;
+ /* make temporary stream (unless this mbx) */
+ if (!stream && !(stream = tstream =
+ mail_open (NIL,mbx,OP_READONLY|OP_SILENT)))
+ return NIL;
+ status.flags = flags; /* return status values */
+ status.messages = stream->nmsgs;
+ status.recent = stream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
+ if (!mail_elt (stream,i)->seen) status.unseen++;
+ status.uidnext = stream->uid_last + 1;
+ status.uidvalidity = stream->uid_validity;
+ /* calculate post-snarf results */
+ if (!status.recent && stream->inbox &&
+ (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) {
+ status.messages += systream->nmsgs;
+ status.recent += systream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1; i <= systream->nmsgs; i++)
+ if (!mail_elt (systream,i)->seen) status.unseen++;
+ /* kludge but probably good enough */
+ status.uidnext += systream->nmsgs;
+ }
+ MM_STATUS(stream,mbx,&status);/* pass status to main program */
+ if (tstream) mail_close (tstream);
+ if (systream) mail_close (systream);
+ return T; /* success */
+}
+
+/* MBX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mbx_open (MAILSTREAM *stream)
+{
+ int fd,ld;
+ short silent;
+ char tmp[MAILTMPLEN];
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&mbxproto);
+ if (stream->local) fatal ("mbx recycle stream");
+ /* canonicalize the mailbox name */
+ if (!mbx_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ MM_LOG (tmp,ERROR);
+ }
+ if (stream->rdonly ||
+ (fd = open (tmp,O_RDWR|O_BINARY,NIL)) < 0) {
+ if ((fd = open (tmp,O_RDONLY|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else if (!stream->rdonly) { /* got it, but readonly */
+ MM_LOG ("Can't get write access to mailbox, access is readonly",WARN);
+ stream->rdonly = T;
+ }
+ }
+
+ stream->local = memset (fs_get (sizeof (MBXLOCAL)),NIL,sizeof (MBXLOCAL));
+ LOCAL->fd = fd; /* bind the file */
+ LOCAL->ld = -1; /* no flaglock */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ /* get parse/append permission */
+ if ((ld = lockfd (LOCAL->fd,tmp,LOCK_EX)) < 0) {
+ MM_LOG ("Unable to lock open mailbox",ERROR);
+ return NIL;
+ }
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH); /* lock the file */
+ (*bn) (BLOCK_NONE,NIL);
+ unlockfd (ld,tmp); /* release shared parse permission */
+ LOCAL->filesize = HDRSIZE; /* initialize parsed file size */
+ /* time not set up yet */
+ LOCAL->lastsnarf = LOCAL->filetime = 0;
+ LOCAL->expok = LOCAL->flagcheck = NIL;
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ silent = stream->silent; /* defer events */
+ stream->silent = T;
+ if (mbx_ping (stream) && !stream->nmsgs)
+ MM_LOG ("Mailbox is empty",(long) NIL);
+ stream->silent = silent; /* now notify upper level */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,stream->recent);
+ if (!LOCAL) return NIL; /* failure if stream died */
+ stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
+ stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ stream->kwd_create = (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ?
+ NIL : T; /* can we create new user flags? */
+ return stream; /* return stream to caller */
+}
+
+/* MBX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mbx_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ /* do an expunge if requested */
+ if (options & CL_EXPUNGE) mbx_expunge (stream,NIL,NIL);
+ else { /* otherwise do a checkpoint to purge */
+ LOCAL->expok = T; /* possible expunged messages */
+ mbx_ping (stream);
+ }
+ stream->silent = silent; /* restore previous status */
+ mbx_abort (stream);
+ }
+}
+
+
+/* MBX mail abort stream
+ * Accepts: MAIL stream
+ */
+
+void mbx_abort (MAILSTREAM *stream)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ flock (LOCAL->fd,LOCK_UN); /* unlock local file */
+ close (LOCAL->fd); /* close the local file */
+ /* free local text buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+
+/* MBX mail fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ * Sniffs at file to see if some other process changed the flags
+ */
+
+void mbx_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned long i;
+ if (mbx_ping (stream) && /* ping mailbox, get new status for messages */
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence && !elt->valid)
+ mbx_elt (stream,i,NIL);
+}
+
+/* MBX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ unsigned long i;
+ char *s;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get header position, possibly header */
+ i = mbx_hdrpos (stream,msgno,length,&s);
+ if (!s) { /* mbx_hdrpos() returned header? */
+ lseek (LOCAL->fd,i,L_SET); /* no, get to header position */
+ /* is buffer big enough? */
+ if (*length > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = *length) + 1);
+ }
+ /* slurp the data */
+ read (LOCAL->fd,s = LOCAL->buf,*length);
+ }
+ s[*length] = '\0'; /* tie off string */
+ return s;
+}
+
+/* MBX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ FDDATA d;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ /* get message status */
+ elt = mbx_elt (stream,msgno,NIL);
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen && mbx_flaglock (stream)) {
+ elt->seen = T; /* mark message as seen */
+ /* recalculate status */
+ mbx_update_status (stream,msgno,NIL);
+ MM_FLAGS (stream,msgno);
+ /* update flags */
+ mbx_flag (stream,NIL,NIL,NIL);
+ }
+ if (!LOCAL) return NIL; /* mbx_flaglock() could have aborted */
+ /* find header position */
+ i = mbx_hdrpos (stream,msgno,&j,NIL);
+ d.fd = LOCAL->fd; /* set up file descriptor */
+ d.pos = i + j;
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE;
+ INIT (bs,fd_string,&d,elt->rfc822_size - j);
+ return LONGT; /* success */
+}
+
+/* MBX mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ * Unlocks flag lock
+ */
+
+void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ time_t tp[2];
+ struct stat sbuf;
+ unsigned long oldpid = LOCAL->lastpid;
+ /* make sure the update takes */
+ if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld >= 0)) {
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ /* we are the last flag updater */
+ LOCAL->lastpid = (unsigned long) getpid ();
+ /* update header if needed */
+ if (((LOCAL->ffuserflag < NUSERFLAGS) &&
+ stream->user_flags[LOCAL->ffuserflag]) || (oldpid != LOCAL->lastpid))
+ mbx_update_header (stream);
+ tp[0] = time (0); /* make sure read comes after all that */
+ utime (stream->mailbox,tp);
+ }
+ if (LOCAL->ld >= 0) { /* unlock now */
+ unlockfd (LOCAL->ld,LOCAL->lock);
+ LOCAL->ld = -1;
+ }
+}
+
+
+/* MBX mail per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ if (mbx_flaglock (stream)) mbx_update_status (stream,elt->msgno,NIL);
+}
+
+/* MBX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long mbx_ping (MAILSTREAM *stream)
+{
+ unsigned long i,pos;
+ long ret = NIL;
+ int ld;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ struct stat sbuf;
+ if (stream && LOCAL) { /* only if stream already open */
+ int snarf = stream->inbox && !stream->rdonly;
+ ret = LONGT; /* assume OK */
+ fstat (LOCAL->fd,&sbuf); /* get current file poop */
+ /* allow expunge if permitted at ping */
+ if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T;
+ /* if external modification */
+ if (LOCAL->filetime && (LOCAL->filetime < sbuf.st_mtime))
+ LOCAL->flagcheck = T; /* upgrade to flag checking */
+ /* new mail or flagcheck handling needed? */
+ if (((sbuf.st_size - LOCAL->filesize) || LOCAL->flagcheck ||
+ !stream->nmsgs || snarf) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) >= 0)) {
+ /* reparse header if not flagchecking */
+ if (!LOCAL->flagcheck) ret = mbx_parse (stream);
+ /* sweep mailbox for changed message status */
+ else if (ret = mbx_parse (stream)) {
+ unsigned long recent = 0;
+ LOCAL->filetime = sbuf.st_mtime;
+ for (i = 1; i <= stream->nmsgs; )
+ if (elt = mbx_elt (stream,i,LOCAL->expok)) {
+ if (elt->recent) ++recent;
+ ++i;
+ }
+ mail_recent (stream,recent);
+ LOCAL->flagcheck = NIL; /* got all the updates */
+ }
+ /* always reparse header at least */
+ if (ret && snarf) { /* snarf new messages if still OK */
+ mbx_snarf (stream);
+ /* parse snarfed messages */
+ ret = mbx_parse (stream);
+ }
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ if (ret) { /* must still be alive */
+ if (!LOCAL->expunged) /* look for holes if none known yet */
+ for (i = 1, pos = HDRSIZE;
+ !LOCAL->expunged && (i <= stream->nmsgs);
+ i++, pos += elt->private.special.text.size + elt->rfc822_size)
+ if ((elt = mail_elt (stream,i))->private.special.offset != pos)
+ LOCAL->expunged = T;/* found a hole */
+ /* burp any holes */
+ if (LOCAL->expunged && !stream->rdonly) {
+ if (mbx_rewrite (stream,&i,NIL)) fatal ("expunge on check");
+ if (i) { /* any space reclaimed? */
+ LOCAL->expunged = NIL;/* no more pending expunge */
+ sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",i);
+ MM_LOG (LOCAL->buf,(long) NIL);
+ }
+ }
+ LOCAL->expok = NIL; /* no more expok */
+ }
+ }
+ return ret; /* return result of the parse */
+}
+
+/* MBX mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void mbx_check (MAILSTREAM *stream)
+{
+ if (LOCAL) LOCAL->expok = T; /* mark that a check is desired */
+ if (mbx_ping (stream)) MM_LOG ("Check completed",(long) NIL);
+}
+
+
+/* MBX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T if success, NIL if failure
+ */
+
+long mbx_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ unsigned long nexp,reclaimed;
+ if (ret = sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) {
+ if (!mbx_ping (stream)); /* do nothing if stream dead */
+ else if (stream->rdonly) /* won't do on readonly files! */
+ MM_LOG ("Expunge ignored on readonly mailbox",WARN);
+ /* if expunged any messages */
+ else if (nexp = mbx_rewrite (stream,&reclaimed,sequence ? -1 : 1)) {
+ sprintf (LOCAL->buf,"Expunged %lu messages",nexp);
+ MM_LOG (LOCAL->buf,(long) NIL);
+ }
+ else if (reclaimed) { /* or if any prior expunged space reclaimed */
+ sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed);
+ MM_LOG (LOCAL->buf,(long) NIL);
+ }
+ else MM_LOG ("No messages deleted, so no update needed",(long) NIL);
+ }
+ return ret;
+}
+
+/* MBX mail snarf messages from system inbox
+ * Accepts: MAIL stream, already locked
+ */
+
+void mbx_snarf (MAILSTREAM *stream)
+{
+ unsigned long i = 0;
+ unsigned long j,r,hdrlen,txtlen;
+ struct stat sbuf;
+ char *hdr,*txt,tmp[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ MAILSTREAM *sysibx = NIL;
+ /* give up if can't get exclusive permission */
+ if ((time (0) >= (LOCAL->lastsnarf +
+ (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) &&
+ strcmp (sysinbox (),stream->mailbox)) {
+ MM_CRITICAL (stream); /* go critical */
+ /* sizes match and anything in sysinbox? */
+ if (!stat (sysinbox (),&sbuf) && sbuf.st_size &&
+ !fstat (LOCAL->fd,&sbuf) && (sbuf.st_size == LOCAL->filesize) &&
+ (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
+ (!sysibx->rdonly) && (r = sysibx->nmsgs)) {
+ /* yes, go to end of file in our mailbox */
+ lseek (LOCAL->fd,sbuf.st_size,L_SET);
+ /* for each message in sysibx mailbox */
+ while (r && (++i <= sysibx->nmsgs)) {
+ /* snarf message from system INBOX */
+ hdr = cpystr (mail_fetchheader_full (sysibx,i,NIL,&hdrlen,NIL));
+ txt = mail_fetchtext_full (sysibx,i,&txtlen,FT_PEEK);
+ /* if have a message */
+ if (j = hdrlen + txtlen) {
+ /* build header line */
+ mail_date (LOCAL->buf,elt = mail_elt (sysibx,i));
+ sprintf (LOCAL->buf + strlen (LOCAL->buf),
+ ",%lu;00000000%04x-00000000\015\012",j,(unsigned)
+ ((fSEEN * elt->seen) +
+ (fDELETED * elt->deleted) + (fFLAGGED * elt->flagged) +
+ (fANSWERED * elt->answered) + (fDRAFT * elt->draft)));
+ /* copy message */
+ if ((write (LOCAL->fd,LOCAL->buf,strlen (LOCAL->buf)) < 0) ||
+ (write (LOCAL->fd,hdr,hdrlen) < 0) ||
+ (write (LOCAL->fd,txt,txtlen) < 0)) r = 0;
+ }
+ fs_give ((void **) &hdr);
+ }
+
+ /* make sure all the updates take */
+ if (fsync (LOCAL->fd)) r = 0;
+ if (r) { /* delete all the messages we copied */
+ if (r == 1) strcpy (tmp,"1");
+ else sprintf (tmp,"1:%lu",r);
+ mail_setflag (sysibx,tmp,"\\Deleted");
+ mail_expunge (sysibx); /* now expunge all those messages */
+ }
+ else {
+ sprintf (LOCAL->buf,"Can't copy new mail: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,WARN);
+ ftruncate (LOCAL->fd,sbuf.st_size);
+ }
+ fstat (LOCAL->fd,&sbuf); /* yes, get current file size */
+ LOCAL->filetime = sbuf.st_mtime;
+ }
+ if (sysibx) mail_close (sysibx);
+ MM_NOCRITICAL (stream); /* release critical */
+ LOCAL->lastsnarf = time (0);/* note time of last snarf */
+ }
+}
+
+/* MBX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ time_t tp[2];
+ MESSAGECACHE *elt;
+ unsigned long i,j,k,m;
+ long ret = LONGT;
+ int fd,ld;
+ char *s,*t,file[MAILTMPLEN],lock[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ MAILSTREAM *dstream = NIL;
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* make sure valid mailbox */
+ if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
+ cu ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid MBX-format mailbox name: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a MBX-format mailbox: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ MM_CRITICAL (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
+
+ /* for each requested message */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.special.text.size,L_SET);
+ mail_date(LOCAL->buf,elt);/* build target header */
+ /* get target keyword mask */
+ for (j = elt->user_flags, k = 0; j; )
+ if (s = stream->user_flags[find_rightmost_bit (&j)])
+ for (m = 0; (m < NUSERFLAGS) && (t = dstream->user_flags[m]); m++)
+ if (!compare_cstring (s,t) && (k |= 1 << m)) break;
+ sprintf (LOCAL->buf+strlen(LOCAL->buf),",%lu;%08lx%04x-%08lx\015\012",
+ elt->rfc822_size,k,(unsigned)
+ ((fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)),cu ? ++dstream->uid_last : 0);
+ /* write target header */
+ if (ret = (write (fd,LOCAL->buf,strlen (LOCAL->buf)) > 0)) {
+ for (k = elt->rfc822_size; ret && (j = min (k,LOCAL->buflen)); k -= j){
+ read (LOCAL->fd,LOCAL->buf,j);
+ ret = write (fd,LOCAL->buf,j) >= 0;
+ }
+ if (cu) { /* need to pass back new UID? */
+ mail_append_set (source,mail_uid (stream,i));
+ mail_append_set (dest,dstream->uid_last);
+ }
+ }
+ }
+
+ /* make sure all the updates take */
+ if (!(ret && (ret = !fsync (fd)))) {
+ sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ }
+ if (cu && ret) { /* return sets if doing COPYUID */
+ (*cu) (stream,mailbox,dstream->uid_validity,source,dest);
+ lseek (fd,15,L_SET); /* update UIDLAST */
+ sprintf (LOCAL->buf,"%08lx",dstream->uid_last);
+ write (fd,LOCAL->buf,8);
+ }
+ else { /* flush any sets we may have built */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */
+ /* else preserve \Marked status */
+ else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
+ tp[1] = sbuf.st_mtime; /* preserve mtime */
+ utime (file,tp); /* set the times */
+ close (fd); /* close the file */
+ MM_NOCRITICAL (stream); /* release critical */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE) && mbx_flaglock (stream)) {
+ for (i = 1; i <= stream->nmsgs; i++) if (mail_elt (stream,i)->sequence) {
+ /* mark message deleted */
+ mbx_elt (stream,i,NIL)->deleted = T;
+ /* recalculate status */
+ mbx_update_status (stream,i,NIL);
+ }
+ /* update flags */
+ mbx_flag (stream,NIL,NIL,NIL);
+ }
+ if (dstream != stream) mail_close (dstream);
+ return ret;
+}
+
+/* MBX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd,ld;
+ char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ time_t tp[2];
+ FILE *df;
+ MESSAGECACHE elt;
+ long f;
+ unsigned long i,uf;
+ STRING *message;
+ long ret = NIL;
+ MAILSTREAM *dstream = NIL;
+ appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ /* make sure valid mailbox */
+ if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
+ au ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* can create INBOX here */
+ mbx_create (dstream = stream ? stream : user_flags (&mbxproto),"INBOX");
+ if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
+ au ? MBXISVALIDUID : MBXISVALIDNOUID)) >= 0)
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid MBX-format mailbox name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MBX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+
+ /* get first message */
+ if (!MM_APPEND (af) (dstream,data,&flags,&date,&message)) close (fd);
+ else if (!(df = fdopen (fd,"r+b"))) {
+ MM_LOG ("Unable to reopen append mailbox",ERROR);
+ close (fd);
+ }
+ else {
+ MM_CRITICAL (dstream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ fseek (df,sbuf.st_size,SEEK_SET);
+ errno = 0;
+ for (ret = LONGT; ret && message; ) {
+ if (!SIZE (message)) { /* guard against zero-length */
+ MM_LOG ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ f = mail_parse_flags (dstream,flags,&uf);
+ if (date) { /* parse date if given */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* mark failure */
+ break;
+ }
+ mail_date (tmp,&elt); /* write preseved date */
+ }
+ else internal_date (tmp); /* get current date in IMAP format */
+ /* write header */
+ if (fprintf (df,"%s,%lu;%08lx%04lx-%08lx\015\012",tmp,i = SIZE (message),
+ uf,(unsigned long) f,au ? ++dstream->uid_last : 0) < 0)
+ ret = NIL;
+ else { /* write message */
+ size_t j;
+ if (!message->cursize) SETPOS (message,GETPOS (message));
+ while (i && (j = fwrite (message->curpos,1,message->cursize,df))) {
+ i -= j;
+ SETPOS (message,GETPOS (message) + j);
+ }
+ /* get next message */
+ if (i || !MM_APPEND (af) (dstream,data,&flags,&date,&message))
+ ret = NIL;
+ else if (au) mail_append_set (dst,dstream->uid_last);
+ }
+ }
+
+ /* if error... */
+ if (!ret || (fflush (df) == EOF)) {
+ /* revert file */
+ ftruncate (fd,sbuf.st_size);
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ if (errno) {
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ ret = NIL;
+ }
+ if (au && ret) { /* return sets if doing APPENDUID */
+ (*au) (mailbox,dstream->uid_validity,dst);
+ fseek (df,15,SEEK_SET); /* update UIDLAST */
+ fprintf (df,"%08lx",dstream->uid_last);
+ }
+ else mail_free_searchset (&dst);
+ /* set atime to now-1 if successful copy */
+ if (ret) tp[0] = time (0) - 1;
+ /* else preserve \Marked status */
+ else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
+ tp[1] = sbuf.st_mtime; /* preserve mtime */
+ utime (file,tp); /* set the times */
+ fclose (df); /* close the file */
+ MM_NOCRITICAL (dstream); /* release critical */
+ }
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ if (dstream != stream) mail_close (dstream);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* MBX mail generate file string
+ * Accepts: temporary buffer to write into
+ * mailbox name string
+ * Returns: local file string or NIL if failure
+ */
+
+char *mbx_file (char *dst,char *name)
+{
+ char *s = mailboxfile (dst,name);
+ return (s && !*s) ? mailboxfile (dst,"~/INBOX") : s;
+}
+
+/* MBX mail parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long mbx_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = NIL;
+ unsigned char c,*s,*t,*x;
+ char tmp[MAILTMPLEN];
+ unsigned long i,j,k,m;
+ off_t curpos = LOCAL->filesize;
+ unsigned long nmsgs = stream->nmsgs;
+ unsigned long recent = stream->recent;
+ unsigned long lastuid = 0;
+ short dirty = NIL;
+ short added = NIL;
+ short silent = stream->silent;
+ short uidwarn = T;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %lu to %lu!",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ lseek (LOCAL->fd,0,L_SET); /* rewind file */
+ /* read internal header */
+ read (LOCAL->fd,LOCAL->buf,HDRSIZE);
+ LOCAL->buf[HDRSIZE] = '\0'; /* tie off header */
+ c = LOCAL->buf[15]; /* save first character of last UID */
+ LOCAL->buf[15] = '\0';
+ /* parse UID validity */
+ stream->uid_validity = strtoul (LOCAL->buf + 7,NIL,16);
+ LOCAL->buf[15] = c; /* restore first character of last UID */
+ /* parse last UID */
+ i = strtoul (LOCAL->buf + 15,NIL,16);
+ stream->uid_last = stream->rdonly ? max (i,stream->uid_last) : i;
+ /* parse user flags */
+ for (i = 0, s = LOCAL->buf + 25;
+ (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s);
+ i++, s = t + 2) {
+ *t = '\0'; /* tie off flag */
+ if (!stream->user_flags[i] && (strlen (s) <= MAXUSERFLAG))
+ stream->user_flags[i] = cpystr (s);
+ }
+ LOCAL->ffuserflag = (int) i; /* first free user flag */
+
+ /* get current last flag updater PID */
+ i = (isxdigit (LOCAL->buf[HDRSIZE-10]) && isxdigit (LOCAL->buf[HDRSIZE-9]) &&
+ isxdigit (LOCAL->buf[HDRSIZE-8]) && isxdigit (LOCAL->buf[HDRSIZE-7]) &&
+ isxdigit (LOCAL->buf[HDRSIZE-6]) && isxdigit (LOCAL->buf[HDRSIZE-5]) &&
+ isxdigit (LOCAL->buf[HDRSIZE-4]) && isxdigit (LOCAL->buf[HDRSIZE-3]) &&
+ (LOCAL->buf[HDRSIZE-2] == '\015') && (LOCAL->buf[HDRSIZE-1] == '\012'))?
+ strtoul (LOCAL->buf + HDRSIZE - 8,NIL,16) : 0;
+ /* set flagcheck if lastpid changed */
+ if (LOCAL->lastpid && (LOCAL->lastpid != i)) LOCAL->flagcheck = T;
+ LOCAL->lastpid = i; /* set as last PID */
+ stream->silent = T; /* don't pass up exists events yet */
+ while (sbuf.st_size - curpos){/* while there is stuff to parse */
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,L_SET);
+ if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
+ sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size,
+ i ? strerror (errno) : "no data read");
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
+ if (!((s = strchr (LOCAL->buf,'\015')) && (s[1] == '\012'))) {
+ sprintf (tmp,"Unable to find CRLF at %lu in %lu bytes, text: %.80s",
+ (unsigned long) curpos,i,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ *s = '\0'; /* tie off header line */
+ i = (s + 2) - LOCAL->buf; /* note start of text offset */
+ if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
+ sprintf (tmp,"Unable to parse internal header at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ if (!(isxdigit (t[1]) && isxdigit (t[2]) && isxdigit (t[3]) &&
+ isxdigit (t[4]) && isxdigit (t[5]) && isxdigit (t[6]) &&
+ isxdigit (t[7]) && isxdigit (t[8]) && isxdigit (t[9]) &&
+ isxdigit (t[10]) && isxdigit (t[11]) && isxdigit (t[12]))) {
+ sprintf (tmp,"Unable to parse message flags at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ if ((t[13] != '-') || t[22] ||
+ !(isxdigit (t[14]) && isxdigit (t[15]) && isxdigit (t[16]) &&
+ isxdigit (t[17]) && isxdigit (t[18]) && isxdigit (t[19]) &&
+ isxdigit (t[20]) && isxdigit (t[21]))) {
+ sprintf (tmp,"Unable to parse message UID at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+
+ *s++ = '\0'; *t++ = '\0'; /* break up fields */
+ /* get message size */
+ if (!(j = strtoul (s,(char **) &x,10)) && (!(x && *x))) {
+ sprintf (tmp,"Unable to parse message size at %lu: %.80s,%.80s;%.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf,(char *) s,
+ (char *) t);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ /* make sure didn't run off end of file */
+ if (((off_t) (curpos + i + j)) > sbuf.st_size) {
+ sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
+ (unsigned long) curpos,(unsigned long) (curpos + i + j),
+ (unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ /* parse UID */
+ if ((m = strtoul (t+13,NIL,16)) &&
+ ((m <= lastuid) || (m > stream->uid_last))) {
+ if (uidwarn) {
+ sprintf (tmp,"Invalid UID %08lx in message %lu, rebuilding UIDs",
+ m,nmsgs+1);
+ MM_LOG (tmp,WARN);
+ uidwarn = NIL;
+ /* restart UID validity */
+ stream->uid_validity = time (0);
+ }
+ m = 0; /* lose this UID */
+ dirty = T; /* mark dirty, set new lastuid */
+ stream->uid_last = lastuid;
+ }
+
+ t[12] = '\0'; /* parse system flags */
+ if ((k = strtoul (t+8,NIL,16)) & fEXPUNGED) {
+ if (m) lastuid = m; /* expunge message, update last UID seen */
+ else { /* no UID assigned? */
+ lastuid = ++stream->uid_last;
+ dirty = T;
+ }
+ }
+ else { /* not expunged, swell the cache */
+ added = T; /* note that a new message was added */
+ mail_exists (stream,++nmsgs);
+ /* instantiate an elt for this message */
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ /* parse the date */
+ if (!mail_parse_date (elt,LOCAL->buf)) {
+ sprintf (tmp,"Unable to parse message date at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* and internal header size */
+ elt->private.special.text.size = i;
+ /* header size not known yet */
+ elt->private.msg.header.text.size = 0;
+ elt->rfc822_size = j; /* note message size */
+ /* calculate system flags */
+ if (k & fSEEN) elt->seen = T;
+ if (k & fDELETED) elt->deleted = T;
+ if (k & fFLAGGED) elt->flagged = T;
+ if (k & fANSWERED) elt->answered = T;
+ if (k & fDRAFT) elt->draft = T;
+ t[8] = '\0'; /* get user flags value */
+ elt->user_flags = strtoul (t,NIL,16);
+ /* UID already assigned? */
+ if (!(elt->private.uid = m) || !(k & fOLD)) {
+ elt->recent = T; /* no, mark as recent */
+ ++recent; /* count up a new recent message */
+ dirty = T; /* and must rewrite header */
+ /* assign new UID */
+ if (!elt->private.uid) elt->private.uid = ++stream->uid_last;
+ mbx_update_status (stream,elt->msgno,NIL);
+ }
+ /* update last parsed UID */
+ lastuid = elt->private.uid;
+ }
+ curpos += i + j; /* update position */
+ }
+
+ if (dirty && !stream->rdonly){/* update header */
+ mbx_update_header (stream);
+ fsync (LOCAL->fd); /* make sure all the UID updates take */
+ }
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
+ LOCAL->filetime = sbuf.st_mtime;
+ if (added && !stream->rdonly){/* make sure atime updated */
+ time_t tp[2];
+ tp[0] = time (0);
+ tp[1] = LOCAL->filetime;
+ utime (stream->mailbox,tp);
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return LONGT; /* return the winnage */
+}
+
+/* MBX get cache element with status updating from file
+ * Accepts: MAIL stream
+ * message number
+ * expunge OK flag
+ * Returns: cache element
+ */
+
+MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok)
+{
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
+ old.answered = elt->answered; old.draft = elt->draft;
+ old.user_flags = elt->user_flags;
+ /* get new flags */
+ if (mbx_read_flags (stream,elt) && expok) {
+ mail_expunged (stream,elt->msgno);
+ return NIL; /* return this message was expunged */
+ }
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
+ (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
+ MM_FLAGS (stream,msgno); /* let top level know */
+ return elt;
+}
+
+/* MBX read flags from file
+ * Accepts: MAIL stream
+ * cache element
+ * Returns: non-NIL if message expunged
+ */
+
+unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ unsigned long i;
+ struct stat sbuf;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* make sure file size is good */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag read!",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ fatal (LOCAL->buf);
+ }
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 24,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,14) < 0) {
+ sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) {
+ LOCAL->buf[14] = '\0'; /* tie off buffer for error message */
+ sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s",
+ elt->msgno,elt->private.special.offset,
+ elt->private.special.text.size,(char *) LOCAL->buf);
+ fatal (LOCAL->buf+50);
+ }
+ LOCAL->buf[13] = '\0'; /* tie off buffer */
+ /* calculate system flags */
+ i = strtoul (LOCAL->buf+9,NIL,16);
+ elt->seen = i & fSEEN ? T : NIL;
+ elt->deleted = i & fDELETED ? T : NIL;
+ elt->flagged = i & fFLAGGED ? T : NIL;
+ elt->answered = i & fANSWERED ? T : NIL;
+ elt->draft = i & fDRAFT ? T : NIL;
+ LOCAL->expunged |= i & fEXPUNGED ? T : NIL;
+ LOCAL->buf[9] = '\0'; /* tie off flags */
+ /* get user flags value */
+ elt->user_flags = strtoul (LOCAL->buf+1,NIL,16);
+ elt->valid = T; /* have valid flags now */
+ return i & fEXPUNGED;
+}
+
+/* MBX update header
+ * Accepts: MAIL stream
+ */
+
+#ifndef CYGKLUDGEOFFSET
+#define CYGKLUDGEOFFSET 0
+#endif
+
+void mbx_update_header (MAILSTREAM *stream)
+{
+ int i;
+ char *s = LOCAL->buf;
+ memset (s,'\0',HDRSIZE); /* initialize header */
+ sprintf (s,"*mbx*\015\012%08lx%08lx\015\012",
+ stream->uid_validity,stream->uid_last);
+ for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; ++i)
+ sprintf (s += strlen (s),"%s\015\012",stream->user_flags[i]);
+ LOCAL->ffuserflag = i; /* first free user flag */
+ /* can we create more user flags? */
+ stream->kwd_create = (i < NUSERFLAGS) ? T : NIL;
+ /* write reserved lines */
+ while (i++ < NUSERFLAGS) strcat (s,"\015\012");
+ sprintf (LOCAL->buf + HDRSIZE - 10,"%08lx\015\012",LOCAL->lastpid);
+ while (T) { /* rewind file */
+ lseek (LOCAL->fd,CYGKLUDGEOFFSET,L_SET);
+ /* write new header */
+ if (write (LOCAL->fd,LOCAL->buf + CYGKLUDGEOFFSET,
+ HDRSIZE - CYGKLUDGEOFFSET) > 0) break;
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+}
+
+/* MBX update status string
+ * Accepts: MAIL stream
+ * message number
+ * flags
+ */
+
+void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ /* readonly */
+ if (stream->rdonly || !elt->valid) mbx_read_flags (stream,elt);
+ else { /* readwrite */
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* make sure file size is good */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag update!",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ fatal (LOCAL->buf);
+ }
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 24,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,14) < 0) {
+ sprintf (LOCAL->buf,"Unable to read old status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) {
+ LOCAL->buf[14] = '\0'; /* tie off buffer for error message */
+ sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s",
+ elt->msgno,elt->private.special.offset,
+ elt->private.special.text.size,(char *) LOCAL->buf);
+ fatal (LOCAL->buf+50);
+ }
+ /* print new flag string */
+ sprintf (LOCAL->buf,"%08lx%04x-%08lx",elt->user_flags,(unsigned)
+ (((elt->deleted && flags) ?
+ fEXPUNGED : (strtoul (LOCAL->buf+9,NIL,16)) & fEXPUNGED) +
+ (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft) + fOLD),elt->private.uid);
+ while (T) { /* get to that place in the file */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 23,L_SET);
+ /* write new flags and UID */
+ if (write (LOCAL->fd,LOCAL->buf,21) > 0) break;
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ }
+}
+
+/* MBX locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * pointer to possible returned header
+ * Returns: position of header in file
+ */
+
+#define HDRBUFLEN 16384 /* good enough for most headers */
+#define SLOP 4 /* CR LF CR LF */
+
+unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size,char **hdr)
+{
+ unsigned long siz,done;
+ long i;
+ unsigned char *s,*t,*te;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ unsigned long ret = elt->private.special.offset +
+ elt->private.special.text.size;
+ if (hdr) *hdr = NIL; /* assume no header returned */
+ /* is header size known? */
+ if (*size = elt->private.msg.header.text.size) return ret;
+ /* paranoia check */
+ if (LOCAL->buflen < (HDRBUFLEN + SLOP))
+ fatal ("LOCAL->buf smaller than HDRBUFLEN");
+ lseek (LOCAL->fd,ret,L_SET); /* get to header position */
+ /* read HDRBUFLEN chunks with 4 byte slop */
+ for (done = siz = 0, s = LOCAL->buf;
+ (i = min ((long) (elt->rfc822_size - done),(long) HDRBUFLEN)) &&
+ (read (LOCAL->fd,s,i) == i);
+ done += i, siz += (t - LOCAL->buf) - SLOP, s = LOCAL->buf + SLOP) {
+ te = (t = s + i) - 12; /* calculate end of fast scan */
+ /* fast scan for CR */
+ for (s = LOCAL->buf; s < te;)
+ if (((*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
+ (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
+ (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
+ (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015')) &&
+ (*s == '\012') && (*++s == '\015') && (*++s == '\012')) {
+ *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf);
+ if (hdr) *hdr = LOCAL->buf;
+ return ret;
+ }
+ for (te = t - 3; (s < te);) /* final character-at-a-time scan */
+ if ((*s++ == '\015') && (*s == '\012') && (*++s == '\015') &&
+ (*++s == '\012')) {
+ *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf);
+ if (hdr) *hdr = LOCAL->buf;
+ return ret;
+ }
+ if (i <= SLOP) break; /* end of data */
+ /* slide over last 4 bytes */
+ memmove (LOCAL->buf,t - SLOP,SLOP);
+ hdr = NIL; /* can't return header this way */
+ }
+ /* not found: header consumes entire message */
+ elt->private.msg.header.text.size = *size = elt->rfc822_size;
+ if (hdr) *hdr = LOCAL->buf; /* possibly return header too */
+ return ret;
+}
+
+/* MBX mail rewrite mailbox
+ * Accepts: MAIL stream
+ * pointer to return reclaimed size
+ * flags (0 = no expunge, 1 = expunge deleted, -1 = expunge sequence)
+ * Returns: number of expunged messages
+ */
+
+unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed,
+ long flags)
+{
+ time_t tp[2];
+ struct stat sbuf;
+ off_t pos,ppos;
+ int ld;
+ unsigned long i,j,k,m,delta;
+ unsigned long n = *reclaimed = 0;
+ unsigned long recent = 0;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* The cretins who designed flock() created a window of vulnerability in
+ * upgrading locks from shared to exclusive or downgrading from exclusive
+ * to shared. Rather than maintain the lock at shared status at a minimum,
+ * flock() actually *releases* the former lock. Obviously they never talked
+ * to any database guys. Fortunately, we have the parse/append permission
+ * lock. If we require this lock before going exclusive on the mailbox,
+ * another process can not sneak in and steal the exclusive mailbox lock on
+ * us, because it will block on trying to get parse/append permission first.
+ */
+ /* get parse/append permission */
+ if ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) < 0) {
+ MM_LOG ("Unable to lock mailbox for rewrite",ERROR);
+ return 0;
+ }
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime && !LOCAL->flagcheck &&
+ (LOCAL->filetime < sbuf.st_mtime)) LOCAL->flagcheck = T;
+ if (!mbx_parse (stream)) { /* make sure see any newly-arrived messages */
+ unlockfd (ld,lock); /* failed?? */
+ return 0;
+ }
+ if (LOCAL->flagcheck) { /* sweep flags if need flagcheck */
+ LOCAL->filetime = sbuf.st_mtime;
+ for (i = 1; i <= stream->nmsgs; ++i) mbx_elt (stream,i,NIL);
+ LOCAL->flagcheck = NIL;
+ }
+
+ /* get exclusive access */
+ if (!flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
+ MM_CRITICAL (stream); /* go critical */
+ for (i = 1,delta = 0,pos = ppos = HDRSIZE; i <= stream->nmsgs; ) {
+ /* note if message not at predicted location */
+ if (m = (elt = mbx_elt (stream,i,NIL))->private.special.offset - ppos) {
+ ppos = elt->private.special.offset;
+ *reclaimed += m; /* note reclaimed message space */
+ delta += m; /* and as expunge delta */
+ }
+ /* number of bytes to smash or preserve */
+ ppos += (k = elt->private.special.text.size + elt->rfc822_size);
+ /* if need to expunge this message*/
+ if (flags && elt->deleted && ((flags > 0) || elt->sequence)) {
+ delta += k; /* number of bytes to delete */
+ mail_expunged(stream,i);/* notify upper levels */
+ n++; /* count up one more expunged message */
+ }
+ else { /* preserved message */
+ i++; /* count this message */
+ if (elt->recent) ++recent;
+ if (delta) { /* moved, note first byte to preserve */
+ j = elt->private.special.offset;
+ do { /* read from source position */
+ m = min (k,LOCAL->buflen);
+ lseek (LOCAL->fd,j,L_SET);
+ read (LOCAL->fd,LOCAL->buf,m);
+ pos = j - delta; /* write to destination position */
+ while (T) {
+ lseek (LOCAL->fd,pos,L_SET);
+ if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ pos += m; /* new position */
+ j += m; /* next chunk, perhaps */
+ } while (k -= m); /* until done */
+ /* note the new address of this text */
+ elt->private.special.offset -= delta;
+ }
+ /* preserved but no deleted messages yet */
+ else pos = elt->private.special.offset + k;
+ }
+ }
+ /* deltaed file size match position? */
+ if (m = (LOCAL->filesize -= delta) - pos) {
+ *reclaimed += m; /* probably an fEXPUNGED msg */
+ LOCAL->filesize = pos; /* set correct size */
+ }
+ /* truncate file after last message */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ fsync (LOCAL->fd); /* force disk update */
+ MM_NOCRITICAL (stream); /* release critical */
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH); /* allow sharers again */
+ (*bn) (BLOCK_NONE,NIL);
+ }
+
+ else { /* can't get exclusive */
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH); /* recover previous shared mailbox lock */
+ (*bn) (BLOCK_NONE,NIL);
+ /* do hide-expunge when shared */
+ if (flags) for (i = 1; i <= stream->nmsgs; ) {
+ if (elt = mbx_elt (stream,i,T)) {
+ /* make the message invisible */
+ if (elt->deleted && ((flags > 0) || elt->sequence)) {
+ mbx_update_status (stream,elt->msgno,LONGT);
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else {
+ i++; /* preserved message */
+ if (elt->recent) ++recent;
+ }
+ }
+ else n++; /* count up one more expunged message */
+ }
+ fsync (LOCAL->fd); /* force disk update */
+ }
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* reset atime to now */
+ utime (stream->mailbox,tp);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ return n; /* return number of expunged messages */
+}
+
+/* MBX mail lock for flag updating
+ * Accepts: stream
+ * Returns: T if successful, NIL if failure
+ */
+
+long mbx_flaglock (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ unsigned long i;
+ int ld;
+ char lock[MAILTMPLEN];
+ /* no-op if readonly or already locked */
+ if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld < 0)) {
+ /* lock now */
+ if ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) < 0) return NIL;
+ if (!LOCAL->flagcheck) { /* don't do this if flagcheck already needed */
+ if (LOCAL->filetime) { /* know previous time? */
+ fstat (LOCAL->fd,&sbuf);/* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->flagcheck = T;
+ LOCAL->filetime = 0; /* don't do this test for any other messages */
+ }
+ if (!mbx_parse (stream)) {/* parse mailbox */
+ unlockfd (ld,lock); /* shouldn't happen */
+ return NIL;
+ }
+ if (LOCAL->flagcheck) /* invalidate cache if flagcheck */
+ for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->valid = NIL;
+ }
+ LOCAL->ld = ld; /* copy to stream for subsequent calls */
+ memcpy (LOCAL->lock,lock,MAILTMPLEN);
+ }
+ return LONGT;
+}
diff --git a/imap/src/osdep/amiga/mh.c b/imap/src/osdep/amiga/mh.c
new file mode 100644
index 00000000..0226b7af
--- /dev/null
+++ b/imap/src/osdep/amiga/mh.c
@@ -0,0 +1,1283 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MH mail routines
+ *
+ * Author(s): Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 23 February 1992
+ * Last Edited: 11 October 2007
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+
+/* Build parameters */
+
+#define MHINBOX "#mhinbox" /* corresponds to namespace in env_unix.c */
+#define MHINBOXDIR "inbox"
+#define MHPROFILE ".mh_profile"
+#define MHCOMMA ','
+#define MHSEQUENCE ".mh_sequence"
+#define MHSEQUENCES ".mh_sequences"
+#define MHPATH "Mail"
+
+
+/* mh_load_message() flags */
+
+#define MLM_HEADER 0x1 /* load message text */
+#define MLM_TEXT 0x2 /* load message text */
+
+/* MH I/O stream local data */
+
+typedef struct mh_local {
+ char *dir; /* spool directory name */
+ unsigned char buf[CHUNKSIZE]; /* temporary buffer */
+ unsigned long cachedtexts; /* total size of all cached texts */
+ time_t scantime; /* last time directory scanned */
+} MHLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MHLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *mh_valid (char *name);
+int mh_isvalid (char *name,char *tmp,long synonly);
+int mh_namevalid (char *name);
+char *mh_path (char *tmp);
+void *mh_parameters (long function,void *value);
+long mh_dirfmttest (char *name);
+void mh_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void mh_list (MAILSTREAM *stream,char *ref,char *pat);
+void mh_lsub (MAILSTREAM *stream,char *ref,char *pat);
+void mh_list_work (MAILSTREAM *stream,char *dir,char *pat,long level);
+long mh_subscribe (MAILSTREAM *stream,char *mailbox);
+long mh_unsubscribe (MAILSTREAM *stream,char *mailbox);
+long mh_create (MAILSTREAM *stream,char *mailbox);
+long mh_delete (MAILSTREAM *stream,char *mailbox);
+long mh_rename (MAILSTREAM *stream,char *old,char *newname);
+MAILSTREAM *mh_open (MAILSTREAM *stream);
+void mh_close (MAILSTREAM *stream,long options);
+void mh_fast (MAILSTREAM *stream,char *sequence,long flags);
+void mh_load_message (MAILSTREAM *stream,unsigned long msgno,long flags);
+char *mh_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags);
+long mh_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+long mh_ping (MAILSTREAM *stream);
+void mh_check (MAILSTREAM *stream);
+long mh_expunge (MAILSTREAM *stream,char *sequence,long options);
+long mh_copy (MAILSTREAM *stream,char *sequence,char *mailbox,
+ long options);
+long mh_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+int mh_select (struct direct *name);
+int mh_numsort (const void *d1,const void *d2);
+char *mh_file (char *dst,char *name);
+long mh_canonicalize (char *pattern,char *ref,char *pat);
+void mh_setdate (char *file,MESSAGECACHE *elt);
+
+/* MH mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mhdriver = {
+ "mh", /* driver name */
+ /* driver flags */
+ DR_MAIL|DR_LOCAL|DR_NOFAST|DR_NAMESPACE|DR_NOSTICKY|DR_DIRFMT,
+ (DRIVER *) NIL, /* next driver */
+ mh_valid, /* mailbox is valid for us */
+ mh_parameters, /* manipulate parameters */
+ mh_scan, /* scan mailboxes */
+ mh_list, /* find mailboxes */
+ mh_lsub, /* find subscribed mailboxes */
+ mh_subscribe, /* subscribe to mailbox */
+ mh_unsubscribe, /* unsubscribe from mailbox */
+ mh_create, /* create mailbox */
+ mh_delete, /* delete mailbox */
+ mh_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ mh_open, /* open mailbox */
+ mh_close, /* close mailbox */
+ mh_fast, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mh_header, /* fetch message header */
+ mh_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mh_ping, /* ping mailbox to see if still alive */
+ mh_check, /* check for new messages */
+ mh_expunge, /* expunge deleted messages */
+ mh_copy, /* copy messages to another mailbox */
+ mh_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mhproto = {&mhdriver};
+
+
+static char *mh_profile = NIL; /* holds MH profile */
+static char *mh_pathname = NIL; /* holds MH path name */
+static long mh_once = 0; /* already snarled once */
+static long mh_allow_inbox =NIL;/* allow INBOX as well as MHINBOX */
+
+/* MH mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mh_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return mh_isvalid (name,tmp,T) ? &mhdriver : NIL;
+}
+
+
+/* MH mail test for valid mailbox
+ * Accepts: mailbox name
+ * temporary buffer to use
+ * syntax only test flag
+ * Returns: T if valid, NIL otherwise
+ */
+
+int mh_isvalid (char *name,char *tmp,long synonly)
+{
+ struct stat sbuf;
+ char *s,*t,altname[MAILTMPLEN];
+ unsigned long i;
+ int ret = NIL;
+ errno = NIL; /* zap any error condition */
+ /* mh name? */
+ if ((mh_allow_inbox && !compare_cstring (name,"INBOX")) ||
+ !compare_cstring (name,MHINBOX) ||
+ ((name[0] == '#') && ((name[1] == 'm') || (name[1] == 'M')) &&
+ ((name[2] == 'h') || (name[2] == 'H')) && (name[3] == '/') && name[4])){
+ if (mh_path (tmp)) /* validate name if INBOX or not synonly */
+ ret = (synonly && compare_cstring (name,"INBOX")) ?
+ T : ((stat (mh_file (tmp,name),&sbuf) == 0) &&
+ (sbuf.st_mode & S_IFMT) == S_IFDIR);
+ else if (!mh_once++) { /* only report error once */
+ sprintf (tmp,"%.900s not found, mh format names disabled",mh_profile);
+ mm_log (tmp,WARN);
+ }
+ }
+ /* see if non-NS name within mh hierarchy */
+ else if ((name[0] != '#') && (s = mh_path (tmp)) && (i = strlen (s)) &&
+ (t = mailboxfile (tmp,name)) && !strncmp (t,s,i) &&
+ (tmp[i] == '/') && tmp[i+1]) {
+ sprintf (altname,"#mh%.900s",tmp+i);
+ /* can't do synonly here! */
+ ret = mh_isvalid (altname,tmp,NIL);
+ }
+ else errno = EINVAL; /* bogus name */
+ return ret;
+}
+
+/* MH mail test for valid mailbox
+ * Accepts: mailbox name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int mh_namevalid (char *name)
+{
+ char *s;
+ if (name[0] == '#' && (name[1] == 'm' || name[1] == 'M') &&
+ (name[2] == 'h' || name[2] == 'H') && name[3] == '/')
+ for (s = name; s && *s;) { /* make sure no all-digit nodes */
+ if (isdigit (*s)) s++; /* digit, check this node further... */
+ else if (*s == '/') break;/* all digit node, barf */
+ /* non-digit, skip to next node or return */
+ else if (!((s = strchr (s+1,'/')) && *++s)) return T;
+ }
+ return NIL; /* all numeric or empty node */
+}
+
+/* Return MH path
+ * Accepts: temporary buffer
+ * Returns: MH path or NIL if MH disabled
+ */
+
+char *mh_path (char *tmp)
+{
+ char *s,*t,*v,*r;
+ int fd;
+ struct stat sbuf;
+ if (!mh_profile) { /* build mh_profile and mh_pathname now */
+ sprintf (tmp,"%s/%s",myhomedir (),MHPROFILE);
+ if ((fd = open (mh_profile = cpystr (tmp),O_RDONLY,NIL)) >= 0) {
+ fstat (fd,&sbuf); /* yes, get size and read file */
+ read (fd,(t = (char *) fs_get (sbuf.st_size + 1)),sbuf.st_size);
+ close (fd); /* don't need the file any more */
+ t[sbuf.st_size] = '\0'; /* tie it off */
+ /* parse profile file */
+ for (s = strtok_r (t,"\r\n",&r); s && *s; s = strtok_r (NIL,"\r\n",&r)) {
+ /* found space in line? */
+ if (v = strpbrk (s," \t")) {
+ *v++ = '\0'; /* tie off, is keyword "Path:"? */
+ if (!compare_cstring (s,"Path:")) {
+ /* skip whitespace */
+ while ((*v == ' ') || (*v == '\t')) ++v;
+ /* absolute path? */
+ if (*v == '/') s = v;
+ else sprintf (s = tmp,"%s/%s",myhomedir (),v);
+ /* copy name */
+ mh_pathname = cpystr (s);
+ break; /* don't need to look at rest of file */
+ }
+ }
+ }
+ fs_give ((void **) &t); /* flush profile text */
+ if (!mh_pathname) { /* default path if not in the profile */
+ sprintf (tmp,"%s/%s",myhomedir (),MHPATH);
+ mh_pathname = cpystr (tmp);
+ }
+ }
+ }
+ return mh_pathname;
+}
+
+/* MH manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mh_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = mh_file ((char *) value,"INBOX");
+ break;
+ case GET_DIRFMTTEST:
+ ret = (void *) mh_dirfmttest;
+ break;
+ case SET_MHPROFILE:
+ if (mh_profile) fs_give ((void **) &mh_profile);
+ mh_profile = cpystr ((char *) value);
+ case GET_MHPROFILE:
+ ret = (void *) mh_profile;
+ break;
+ case SET_MHPATH:
+ if (mh_pathname) fs_give ((void **) &mh_pathname);
+ mh_pathname = cpystr ((char *) value);
+ case GET_MHPATH:
+ ret = (void *) mh_pathname;
+ break;
+ case SET_MHALLOWINBOX:
+ mh_allow_inbox = value ? T : NIL;
+ case GET_MHALLOWINBOX:
+ ret = (void *) (mh_allow_inbox ? VOIDT : NIL);
+ }
+ return ret;
+}
+
+
+/* MH test for directory format internal node
+ * Accepts: candidate node name
+ * Returns: T if internal name, NIL otherwise
+ */
+
+long mh_dirfmttest (char *s)
+{
+ int c;
+ /* sequence(s) file is an internal name */
+ if (strcmp (s,MHSEQUENCE) && strcmp (s,MHSEQUENCES)) {
+ if (*s == MHCOMMA) ++s; /* else comma + all numeric name */
+ /* success if all-numeric */
+ while (c = *s++) if (!isdigit (c)) return NIL;
+ }
+ return LONGT;
+}
+
+/* MH scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mh_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ char *s,test[MAILTMPLEN],file[MAILTMPLEN];
+ long i = 0;
+ if (!pat || !*pat) { /* empty pattern? */
+ if (mh_canonicalize (test,ref,"*")) {
+ /* tie off name at root */
+ if (s = strchr (test,'/')) *++s = '\0';
+ else test[0] = '\0';
+ mm_list (stream,'/',test,LATT_NOSELECT);
+ }
+ }
+ /* get canonical form of name */
+ else if (mh_canonicalize (test,ref,pat)) {
+ if (contents) { /* maybe I'll implement this someday */
+ mm_log ("Scan not valid for mh mailboxes",ERROR);
+ return;
+ }
+ if (test[3] == '/') { /* looking down levels? */
+ /* yes, found any wildcards? */
+ if (s = strpbrk (test,"%*")) {
+ /* yes, copy name up to that point */
+ strncpy (file,test+4,i = s - (test+4));
+ file[i] = '\0'; /* tie off */
+ }
+ else strcpy (file,test+4);/* use just that name then */
+ /* find directory name */
+ if (s = strrchr (file,'/')) {
+ *s = '\0'; /* found, tie off at that point */
+ s = file;
+ }
+ /* do the work */
+ mh_list_work (stream,s,test,0);
+ }
+ /* always an INBOX */
+ if (!compare_cstring (test,MHINBOX))
+ mm_list (stream,NIL,MHINBOX,LATT_NOINFERIORS);
+ }
+}
+
+/* MH list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mh_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ mh_scan (stream,ref,pat,NIL);
+}
+
+
+/* MH list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mh_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ void *sdb = NIL;
+ char *s,test[MAILTMPLEN];
+ /* get canonical form of name */
+ if (mh_canonicalize (test,ref,pat) && (s = sm_read (&sdb))) {
+ do if (pmatch_full (s,test,'/')) mm_lsub (stream,'/',s,NIL);
+ while (s = sm_read (&sdb)); /* until no more subscriptions */
+ }
+}
+
+/* MH list mailboxes worker routine
+ * Accepts: mail stream
+ * directory name to search
+ * search pattern
+ * search level
+ */
+
+void mh_list_work (MAILSTREAM *stream,char *dir,char *pat,long level)
+{
+ DIR *dp;
+ struct direct *d;
+ struct stat sbuf;
+ char *cp,*np,curdir[MAILTMPLEN],name[MAILTMPLEN];
+ /* build MH name to search */
+ if (dir) sprintf (name,"#mh/%s/",dir);
+ else strcpy (name,"#mh/");
+ /* make directory name, punt if bogus */
+ if (!mh_file (curdir,name)) return;
+ cp = curdir + strlen (curdir);/* end of directory name */
+ np = name + strlen (name); /* end of MH name */
+ if (dp = opendir (curdir)) { /* open directory */
+ while (d = readdir (dp)) /* scan, ignore . and numeric names */
+ if ((d->d_name[0] != '.') && !mh_select (d)) {
+ strcpy (cp,d->d_name); /* make directory name */
+ if (!stat (curdir,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
+ strcpy (np,d->d_name);/* make mh name of directory name */
+ /* yes, an MH name if full match */
+ if (pmatch_full (name,pat,'/')) mm_list (stream,'/',name,NIL);
+ /* check if should recurse */
+ if (dmatch (name,pat,'/') &&
+ (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL)))
+ mh_list_work (stream,name+4,pat,level+1);
+ }
+ }
+ closedir (dp); /* all done, flush directory */
+ }
+}
+
+/* MH mail subscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to add to subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long mh_subscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return sm_subscribe (mailbox);
+}
+
+
+/* MH mail unsubscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to delete from subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long mh_unsubscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return sm_unsubscribe (mailbox);
+}
+
+/* MH mail create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mh_create (MAILSTREAM *stream,char *mailbox)
+{
+ char tmp[MAILTMPLEN];
+ if (!mh_namevalid (mailbox)) /* validate name */
+ sprintf (tmp,"Can't create mailbox %.80s: invalid MH-format name",mailbox);
+ /* must not already exist */
+ else if (mh_isvalid (mailbox,tmp,NIL))
+ sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox);
+ else if (!mh_path (tmp)) return NIL;
+ /* try to make it */
+ else if (!(mh_file (tmp,mailbox) &&
+ dummy_create_path (stream,strcat (tmp,"/"),
+ get_dir_protection (mailbox))))
+ sprintf (tmp,"Can't create mailbox %.80s: %s",mailbox,strerror (errno));
+ else return LONGT; /* success */
+ mm_log (tmp,ERROR);
+ return NIL;
+}
+
+/* MH mail delete mailbox
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mh_delete (MAILSTREAM *stream,char *mailbox)
+{
+ DIR *dirp;
+ struct direct *d;
+ int i;
+ char tmp[MAILTMPLEN];
+ /* is mailbox valid? */
+ if (!mh_isvalid (mailbox,tmp,NIL)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get name of directory */
+ i = strlen (mh_file (tmp,mailbox));
+ if (dirp = opendir (tmp)) { /* open directory */
+ tmp[i++] = '/'; /* now apply trailing delimiter */
+ /* massacre all mh owned files */
+ while (d = readdir (dirp)) if (mh_dirfmttest (d->d_name)) {
+ strcpy (tmp + i,d->d_name);
+ unlink (tmp); /* sayonara */
+ }
+ closedir (dirp); /* flush directory */
+ }
+ /* try to remove the directory */
+ if (rmdir (mh_file (tmp,mailbox))) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",mailbox,strerror (errno));
+ mm_log (tmp,WARN);
+ }
+ return T; /* return success */
+}
+
+/* MH mail rename mailbox
+ * Accepts: MH mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long mh_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN];
+ struct stat sbuf;
+ /* old mailbox name must be valid */
+ if (!mh_isvalid (old,tmp,NIL))
+ sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old);
+ else if (!mh_namevalid (newname))
+ sprintf (tmp,"Can't rename to mailbox %.80s: invalid MH-format name",
+ newname);
+ /* new mailbox name must not be valid */
+ else if (mh_isvalid (newname,tmp,NIL))
+ sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists",
+ newname);
+ /* success if can rename the directory */
+ else { /* found superior to destination name? */
+ if (s = strrchr (mh_file (tmp1,newname),'/')) {
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp1,get_dir_protection (newname)))
+ return NIL;
+ *s = c; /* restore full name */
+ }
+ if (!rename (mh_file (tmp,old),tmp1)) return T;
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",
+ old,newname,strerror (errno));
+ }
+ mm_log (tmp,ERROR); /* something failed */
+ return NIL;
+}
+
+/* MH mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mh_open (MAILSTREAM *stream)
+{
+ char tmp[MAILTMPLEN];
+ if (!stream) return &mhproto; /* return prototype for OP_PROTOTYPE call */
+ if (stream->local) fatal ("mh recycle stream");
+ stream->local = fs_get (sizeof (MHLOCAL));
+ /* INBOXness is one of the following:
+ * #mhinbox (case-independent)
+ * #mh/inbox (mh is case-independent, inbox is case-dependent)
+ * INBOX (case-independent
+ */
+ stream->inbox = /* note if an INBOX or not */
+ (!compare_cstring (stream->mailbox,MHINBOX) ||
+ ((stream->mailbox[0] == '#') &&
+ ((stream->mailbox[1] == 'm') || (stream->mailbox[1] == 'M')) &&
+ ((stream->mailbox[2] == 'h') || (stream->mailbox[2] == 'H')) &&
+ (stream->mailbox[3] == '/') && !strcmp (stream->mailbox+4,MHINBOXDIR)) ||
+ !compare_cstring (stream->mailbox,"INBOX")) ? T : NIL;
+ mh_file (tmp,stream->mailbox);/* get directory name */
+ LOCAL->dir = cpystr (tmp); /* copy directory name for later */
+ LOCAL->scantime = 0; /* not scanned yet */
+ LOCAL->cachedtexts = 0; /* no cached texts */
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (!mh_ping (stream)) return NIL;
+ if (!(stream->nmsgs || stream->silent))
+ mm_log ("Mailbox is empty",(long) NIL);
+ return stream; /* return stream to caller */
+}
+
+/* MH mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mh_close (MAILSTREAM *stream,long options)
+{
+ if (LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ if (options & CL_EXPUNGE) mh_expunge (stream,NIL,NIL);
+ if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ stream->silent = silent; /* reset silent state */
+ }
+}
+
+
+/* MH mail fetch fast information
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ */
+
+void mh_fast (MAILSTREAM *stream,char *sequence,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned long i;
+ /* set up metadata for all messages */
+ if (stream && LOCAL && ((flags & FT_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence &&
+ !(elt->day && elt->rfc822_size)) mh_load_message (stream,i,NIL);
+}
+
+/* MH load message into cache
+ * Accepts: MAIL stream
+ * message #
+ * option flags
+ */
+
+void mh_load_message (MAILSTREAM *stream,unsigned long msgno,long flags)
+{
+ unsigned long i,j,nlseen;
+ int fd;
+ unsigned char c,*t;
+ struct stat sbuf;
+ MESSAGECACHE *elt;
+ FDDATA d;
+ STRING bs;
+ elt = mail_elt (stream,msgno);/* get elt */
+ /* build message file name */
+ sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
+ /* anything we need not currently cached? */
+ if ((!elt->day || !elt->rfc822_size ||
+ ((flags & MLM_HEADER) && !elt->private.msg.header.text.data) ||
+ ((flags & MLM_TEXT) && !elt->private.msg.text.text.data)) &&
+ ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0)) {
+ fstat (fd,&sbuf); /* get file metadata */
+ d.fd = fd; /* set up file descriptor */
+ d.pos = 0; /* start of file */
+ d.chunk = LOCAL->buf;
+ d.chunksize = CHUNKSIZE;
+ INIT (&bs,fd_string,&d,sbuf.st_size);
+ if (!elt->day) { /* set internaldate to file date */
+ struct tm *tm = gmtime (&sbuf.st_mtime);
+ elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
+ elt->year = tm->tm_year + 1900 - BASEYEAR;
+ elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
+ elt->seconds = tm->tm_sec;
+ elt->zhours = 0; elt->zminutes = 0;
+ }
+
+ if (!elt->rfc822_size) { /* know message size yet? */
+ for (i = 0, j = SIZE (&bs), nlseen = 0; j--; ) switch (SNX (&bs)) {
+ case '\015': /* unlikely carriage return */
+ if (!j || (CHR (&bs) != '\012')) {
+ i++; /* ugh, raw CR */
+ nlseen = NIL;
+ break;
+ }
+ SNX (&bs); /* eat the line feed, drop in */
+ --j;
+ case '\012': /* line feed? */
+ i += 2; /* count a CRLF */
+ /* header size known yet? */
+ if (!elt->private.msg.header.text.size && nlseen) {
+ /* note position in file */
+ elt->private.special.text.size = GETPOS (&bs);
+ /* and CRLF-adjusted size */
+ elt->private.msg.header.text.size = i;
+ }
+ nlseen = T; /* note newline seen */
+ break;
+ default: /* ordinary chararacter */
+ i++;
+ nlseen = NIL;
+ break;
+ }
+ SETPOS (&bs,0); /* restore old position */
+ elt->rfc822_size = i; /* note that we have size now */
+ /* header is entire message if no delimiter */
+ if (!elt->private.msg.header.text.size)
+ elt->private.msg.header.text.size = elt->rfc822_size;
+ /* text is remainder of message */
+ elt->private.msg.text.text.size =
+ elt->rfc822_size - elt->private.msg.header.text.size;
+ }
+ /* need to load cache with message data? */
+ if (((flags & MLM_HEADER) && !elt->private.msg.header.text.data) ||
+ ((flags & MLM_TEXT) && !elt->private.msg.text.text.data)) {
+ /* purge cache if too big */
+ if (LOCAL->cachedtexts > max (stream->nmsgs * 4096,2097152)) {
+ /* just can't keep that much */
+ mail_gc (stream,GC_TEXTS);
+ LOCAL->cachedtexts = 0;
+ }
+
+ if ((flags & MLM_HEADER) && !elt->private.msg.header.text.data) {
+ t = elt->private.msg.header.text.data =
+ (unsigned char *) fs_get (elt->private.msg.header.text.size + 1);
+ LOCAL->cachedtexts += elt->private.msg.header.text.size;
+ /* read in message header */
+ for (i = 0; i < elt->private.msg.header.text.size; i++)
+ switch (c = SNX (&bs)) {
+ case '\015': /* unlikely carriage return */
+ *t++ = c;
+ if ((CHR (&bs) == '\012')) {
+ *t++ = SNX (&bs);
+ i++;
+ }
+ break;
+ case '\012': /* line feed? */
+ *t++ = '\015';
+ i++;
+ default:
+ *t++ = c;
+ break;
+ }
+ *t = '\0'; /* tie off string */
+ if ((t - elt->private.msg.header.text.data) !=
+ elt->private.msg.header.text.size) fatal ("mh hdr size mismatch");
+ }
+ if ((flags & MLM_TEXT) && !elt->private.msg.text.text.data) {
+ t = elt->private.msg.text.text.data =
+ (unsigned char *) fs_get (elt->private.msg.text.text.size + 1);
+ SETPOS (&bs,elt->private.special.text.size);
+ LOCAL->cachedtexts += elt->private.msg.text.text.size;
+ /* read in message text */
+ for (i = 0; i < elt->private.msg.text.text.size; i++)
+ switch (c = SNX (&bs)) {
+ case '\015': /* unlikely carriage return */
+ *t++ = c;
+ if ((CHR (&bs) == '\012')) {
+ *t++ = SNX (&bs);
+ i++;
+ }
+ break;
+ case '\012': /* line feed? */
+ *t++ = '\015';
+ i++;
+ default:
+ *t++ = c;
+ break;
+ }
+ *t = '\0'; /* tie off string */
+ if ((t - elt->private.msg.text.text.data) !=
+ elt->private.msg.text.text.size) fatal ("mh txt size mismatch");
+ }
+ }
+ close (fd); /* flush message file */
+ }
+}
+
+/* MH mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mh_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ MESSAGECACHE *elt;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get elt */
+ if (!elt->private.msg.header.text.data)
+ mh_load_message (stream,msgno,MLM_HEADER);
+ *length = elt->private.msg.header.text.size;
+ return (char *) elt->private.msg.header.text.data;
+}
+
+
+/* MH mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mh_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);/* get elt */
+ /* snarf message if don't have it yet */
+ if (!elt->private.msg.text.text.data) {
+ mh_load_message (stream,msgno,MLM_TEXT);
+ if (!elt->private.msg.text.text.data) return NIL;
+ }
+ if (!(flags & FT_PEEK)) { /* mark as seen */
+ mail_elt (stream,msgno)->seen = T;
+ mm_flags (stream,msgno);
+ }
+ INIT (bs,mail_string,elt->private.msg.text.text.data,
+ elt->private.msg.text.text.size);
+ return T;
+}
+
+/* MH mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long mh_ping (MAILSTREAM *stream)
+{
+ MAILSTREAM *sysibx = NIL;
+ MESSAGECACHE *elt,*selt;
+ struct stat sbuf;
+ char *s,tmp[MAILTMPLEN];
+ int fd;
+ unsigned long i,j,r;
+ unsigned long old = stream->uid_last;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ int silent = stream->silent;
+ if (stat (LOCAL->dir,&sbuf)) {/* directory exists? */
+ if (stream->inbox && /* no, create if INBOX */
+ dummy_create_path (stream,strcat (mh_file (tmp,MHINBOX),"/"),
+ get_dir_protection ("INBOX"))) return T;
+ sprintf (tmp,"Can't open mailbox %.80s: no such mailbox",stream->mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ stream->silent = T; /* don't pass up mm_exists() events yet */
+ if (sbuf.st_ctime != LOCAL->scantime) {
+ struct direct **names = NIL;
+ long nfiles = scandir (LOCAL->dir,&names,mh_select,mh_numsort);
+ if (nfiles < 0) nfiles = 0; /* in case error */
+ /* note scanned now */
+ LOCAL->scantime = sbuf.st_ctime;
+ /* scan directory */
+ for (i = 0; i < nfiles; ++i) {
+ /* if newly seen, add to list */
+ if ((j = atoi (names[i]->d_name)) > old) {
+ mail_exists (stream,++nmsgs);
+ stream->uid_last = (elt = mail_elt (stream,nmsgs))->private.uid = j;
+ elt->valid = T; /* note valid flags */
+ if (old) { /* other than the first pass? */
+ elt->recent = T; /* yup, mark as recent */
+ recent++; /* bump recent count */
+ }
+ else { /* see if already read */
+ sprintf (tmp,"%s/%s",LOCAL->dir,names[i]->d_name);
+ if (!stat (tmp,&sbuf) && (sbuf.st_atime > sbuf.st_mtime))
+ elt->seen = T;
+ }
+ }
+ fs_give ((void **) &names[i]);
+ }
+ /* free directory */
+ if (s = (void *) names) fs_give ((void **) &s);
+ }
+
+ /* if INBOX, snarf from system INBOX */
+ if (stream->inbox && strcmp (sysinbox (),stream->mailbox)) {
+ old = stream->uid_last;
+ mm_critical (stream); /* go critical */
+ /* see if anything in system inbox */
+ if (!stat (sysinbox (),&sbuf) && sbuf.st_size &&
+ (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
+ !sysibx->rdonly && (r = sysibx->nmsgs)) {
+ for (i = 1; i <= r; ++i) {/* for each message in sysinbox mailbox */
+ /* build file name we will use */
+ sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,++old);
+ /* snarf message from Berkeley mailbox */
+ selt = mail_elt (sysibx,i);
+ if (((fd = open (LOCAL->buf,O_WRONLY|O_CREAT|O_EXCL,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL)))
+ >= 0) &&
+ (s = mail_fetchheader_full (sysibx,i,NIL,&j,FT_INTERNAL)) &&
+ (write (fd,s,j) == j) &&
+ (s = mail_fetchtext_full (sysibx,i,&j,FT_INTERNAL|FT_PEEK)) &&
+ (write (fd,s,j) == j) && !fsync (fd) && !close (fd)) {
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ stream->uid_last = /* create new elt, note its file number */
+ (elt = mail_elt (stream,nmsgs))->private.uid = old;
+ recent++; /* bump recent count */
+ /* set up initial flags and date */
+ elt->valid = elt->recent = T;
+ elt->seen = selt->seen;
+ elt->deleted = selt->deleted;
+ elt->flagged = selt->flagged;
+ elt->answered = selt->answered;
+ elt->draft = selt->draft;
+ elt->day = selt->day;elt->month = selt->month;elt->year = selt->year;
+ elt->hours = selt->hours;elt->minutes = selt->minutes;
+ elt->seconds = selt->seconds;
+ elt->zhours = selt->zhours; elt->zminutes = selt->zminutes;
+ elt->zoccident = selt->zoccident;
+ mh_setdate (LOCAL->buf,elt);
+ sprintf (tmp,"%lu",i);/* delete it from the sysinbox */
+ mail_flag (sysibx,tmp,"\\Deleted",ST_SET);
+ }
+
+ else { /* failed to snarf */
+ if (fd) { /* did it ever get opened? */
+ close (fd); /* close descriptor */
+ unlink (LOCAL->buf);/* flush this file */
+ }
+ sprintf (tmp,"Message copy to MH mailbox failed: %.80s",
+ s,strerror (errno));
+ mm_log (tmp,ERROR);
+ r = 0; /* stop the snarf in its tracks */
+ }
+ }
+ /* update scan time */
+ if (!stat (LOCAL->dir,&sbuf)) LOCAL->scantime = sbuf.st_ctime;
+ mail_expunge (sysibx); /* now expunge all those messages */
+ }
+ if (sysibx) mail_close (sysibx);
+ mm_nocritical (stream); /* release critical */
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of mailbox size */
+ mail_recent (stream,recent);
+ return T; /* return that we are alive */
+}
+
+/* MH mail check mailbox
+ * Accepts: MAIL stream
+ */
+
+void mh_check (MAILSTREAM *stream)
+{
+ /* Perhaps in the future this will preserve flags */
+ if (mh_ping (stream)) mm_log ("Check completed",(long) NIL);
+}
+
+
+/* MH mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long mh_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ MESSAGECACHE *elt;
+ unsigned long i = 1;
+ unsigned long n = 0;
+ unsigned long recent = stream->recent;
+ if (ret = sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) {
+ mm_critical (stream); /* go critical */
+ while (i <= stream->nmsgs) {/* for each message */
+ elt = mail_elt (stream,i);/* if deleted, need to trash it */
+ if (elt->deleted && (sequence ? elt->sequence : T)) {
+ sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
+ if (unlink (LOCAL->buf)) {/* try to delete the message */
+ sprintf (LOCAL->buf,"Expunge of message %lu failed, aborted: %s",i,
+ strerror (errno));
+ mm_log (LOCAL->buf,(long) NIL);
+ break;
+ }
+ /* note uncached */
+ LOCAL->cachedtexts -= ((elt->private.msg.header.text.data ?
+ elt->private.msg.header.text.size : 0) +
+ (elt->private.msg.text.text.data ?
+ elt->private.msg.text.text.size : 0));
+ mail_gc_msg (&elt->private.msg,GC_ENV | GC_TEXTS);
+ /* if recent, note one less recent message */
+ if (elt->recent) --recent;
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else i++; /* otherwise try next message */
+ }
+ if (n) { /* output the news if any expunged */
+ sprintf (LOCAL->buf,"Expunged %lu messages",n);
+ mm_log (LOCAL->buf,(long) NIL);
+ }
+ else mm_log ("No messages deleted, so no update needed",(long) NIL);
+ mm_nocritical (stream); /* release critical */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ }
+ return ret;
+}
+
+/* MH mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long mh_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ FDDATA d;
+ STRING st;
+ MESSAGECACHE *elt;
+ struct stat sbuf;
+ int fd;
+ unsigned long i;
+ char flags[MAILTMPLEN],date[MAILTMPLEN];
+ appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
+ long ret = NIL;
+ /* copy the messages */
+ if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
+ if ((fd = open (LOCAL->buf,O_RDONLY,NIL)) < 0) return NIL;
+ fstat (fd,&sbuf); /* get size of message */
+ if (!elt->day) { /* set internaldate to file date if needed */
+ struct tm *tm = gmtime (&sbuf.st_mtime);
+ elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
+ elt->year = tm->tm_year + 1900 - BASEYEAR;
+ elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
+ elt->seconds = tm->tm_sec;
+ elt->zhours = 0; elt->zminutes = 0;
+ }
+ d.fd = fd; /* set up file descriptor */
+ d.pos = 0; /* start of file */
+ d.chunk = LOCAL->buf;
+ d.chunksize = CHUNKSIZE;
+ /* kludge; mh_append would just strip CRs */
+ INIT (&st,fd_string,&d,sbuf.st_size);
+ /* init flag string */
+ flags[0] = flags[1] = '\0';
+ if (elt->seen) strcat (flags," \\Seen");
+ if (elt->deleted) strcat (flags," \\Deleted");
+ if (elt->flagged) strcat (flags," \\Flagged");
+ if (elt->answered) strcat (flags," \\Answered");
+ if (elt->draft) strcat (flags," \\Draft");
+ flags[0] = '('; /* open list */
+ strcat (flags,")"); /* close list */
+ mail_date (date,elt); /* generate internal date */
+ if (au) mail_parameters (NIL,SET_APPENDUID,NIL);
+ if ((ret = mail_append_full (NIL,mailbox,flags,date,&st)) &&
+ (options & CP_MOVE)) elt->deleted = T;
+ if (au) mail_parameters (NIL,SET_APPENDUID,(void *) au);
+ close (fd);
+ }
+ if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
+ mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN);
+ return ret; /* return success */
+}
+
+/* MH mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mh_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct direct **names = NIL;
+ int fd;
+ char c,*flags,*date,*s,tmp[MAILTMPLEN];
+ STRING *message;
+ MESSAGECACHE elt;
+ FILE *df;
+ long i,size,last,nfiles;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = &mhproto;
+ /* make sure valid mailbox */
+ if (!mh_isvalid (mailbox,tmp,NIL)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!((!compare_cstring (mailbox,MHINBOX) ||
+ !compare_cstring (mailbox,"INBOX")) &&
+ (mh_file (tmp,MHINBOX) &&
+ dummy_create_path (stream,strcat (tmp,"/"),
+ get_dir_protection (mailbox))))) {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* falls through */
+ case 0: /* merely empty file? */
+ break;
+ case EINVAL:
+ sprintf (tmp,"Invalid MH-format mailbox name: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MH-format mailbox: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get first message */
+ if (!(*af) (stream,data,&flags,&date,&message)) return NIL;
+ if ((nfiles = scandir (tmp,&names,mh_select,mh_numsort)) > 0) {
+ /* largest number */
+ last = atoi (names[nfiles-1]->d_name);
+ for (i = 0; i < nfiles; ++i) /* free directory */
+ fs_give ((void **) &names[i]);
+ }
+ else last = 0; /* no messages here yet */
+ if (s = (void *) names) fs_give ((void **) &s);
+
+ mm_critical (stream); /* go critical */
+ do {
+ if (!SIZE (message)) { /* guard against zero-length */
+ mm_log ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ if (date) { /* want to preserve date? */
+ /* yes, parse date into an elt */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ mm_log (tmp,ERROR);
+ ret = NIL;
+ break;
+ }
+ }
+ mh_file (tmp,mailbox); /* build file name we will use */
+ sprintf (tmp + strlen (tmp),"/%ld",++last);
+ if (((fd = open (tmp,O_WRONLY|O_CREAT|O_EXCL,
+ (long)mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0)||
+ !(df = fdopen (fd,"ab"))) {
+ sprintf (tmp,"Can't open append message: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL;
+ break;
+ }
+ /* copy the data w/o CR's */
+ for (size = 0,i = SIZE (message); i && ret; --i)
+ if (((c = SNX (message)) != '\015') && (putc (c,df) == EOF)) ret = NIL;
+ /* close the file */
+ if (!ret || fclose (df)) {
+ unlink (tmp); /* delete message */
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL;
+ }
+ if (ret) { /* set the date for this message */
+ if (date) mh_setdate (tmp,&elt);
+ /* get next message */
+ if (!(*af) (stream,data,&flags,&date,&message)) ret = NIL;
+ }
+ } while (ret && message);
+ mm_nocritical (stream); /* release critical */
+ if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
+ mm_log ("Can not return meaningful APPENDUID with this mailbox format",
+ WARN);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* MH file name selection test
+ * Accepts: candidate directory entry
+ * Returns: T to use file name, NIL to skip it
+ */
+
+int mh_select (struct direct *name)
+{
+ char c;
+ char *s = name->d_name;
+ while (c = *s++) if (!isdigit (c)) return NIL;
+ return T;
+}
+
+
+/* MH file name comparision
+ * Accepts: first candidate directory entry
+ * second candidate directory entry
+ * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
+ */
+
+int mh_numsort (const void *d1,const void *d2)
+{
+ return atoi ((*(struct direct **) d1)->d_name) -
+ atoi ((*(struct direct **) d2)->d_name);
+}
+
+
+/* MH mail build file name
+ * Accepts: destination string
+ * source
+ * Returns: destination
+ */
+
+char *mh_file (char *dst,char *name)
+{
+ char *s;
+ char *path = mh_path (dst);
+ if (!path) fatal ("No mh path in mh_file()!");
+ /* INBOX becomes "inbox" in the MH path */
+ if (!compare_cstring (name,MHINBOX) || !compare_cstring (name,"INBOX"))
+ sprintf (dst,"%.900s/%.80s",path,MHINBOXDIR);
+ /* #mh names skip past prefix */
+ else if (*name == '#') sprintf (dst,"%.100s/%.900s",path,name + 4);
+ else mailboxfile (dst,name); /* all other names */
+ /* tie off unnecessary trailing / */
+ if ((s = strrchr (dst,'/')) && !s[1] && (s[-1] == '/')) *s = '\0';
+ return dst;
+}
+
+/* MH canonicalize name
+ * Accepts: buffer to write name
+ * reference
+ * pattern
+ * Returns: T if success, NIL if failure
+ */
+
+long mh_canonicalize (char *pattern,char *ref,char *pat)
+{
+ unsigned long i;
+ char *s,tmp[MAILTMPLEN];
+ if (ref && *ref) { /* have a reference */
+ strcpy (pattern,ref); /* copy reference to pattern */
+ /* # overrides mailbox field in reference */
+ if (*pat == '#') strcpy (pattern,pat);
+ /* pattern starts, reference ends, with / */
+ else if ((*pat == '/') && (pattern[strlen (pattern) - 1] == '/'))
+ strcat (pattern,pat + 1); /* append, omitting one of the period */
+ else strcat (pattern,pat); /* anything else is just appended */
+ }
+ else strcpy (pattern,pat); /* just have basic name */
+ if (mh_isvalid (pattern,tmp,T)) {
+ /* count wildcards */
+ for (i = 0, s = pattern; *s; *s++) if ((*s == '*') || (*s == '%')) ++i;
+ /* success if not too many */
+ if (i <= MAXWILDCARDS) return LONGT;
+ mm_log ("Excessive wildcards in LIST/LSUB",ERROR);
+ }
+ return NIL;
+}
+
+/* Set date for message
+ * Accepts: file name
+ * elt containing date
+ */
+
+void mh_setdate (char *file,MESSAGECACHE *elt)
+{
+ time_t tp[2];
+ tp[0] = time (0); /* atime is now */
+ tp[1] = mail_longdate (elt); /* modification time */
+ utime (file,tp); /* set the times */
+}
diff --git a/imap/src/osdep/amiga/mix.c b/imap/src/osdep/amiga/mix.c
new file mode 100644
index 00000000..fbf4a023
--- /dev/null
+++ b/imap/src/osdep/amiga/mix.c
@@ -0,0 +1,2834 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MIX mail routines
+ *
+ * Author(s): Mark Crispin
+ * UW Technology
+ * University of Washington
+ * Seattle, WA 98195
+ * Internet: MRC@Washington.EDU
+ *
+ * Date: 1 March 2006
+ * Last Edited: 7 May 2008
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+/* MIX definitions */
+
+#define MEGABYTE (1024*1024)
+
+#define MIXDATAROLL MEGABYTE /* size at which we roll to a new file */
+
+
+/* MIX files */
+
+#define MIXNAME ".mix" /* prefix for all MIX file names */
+#define MIXMETA "meta" /* suffix for metadata */
+#define MIXINDEX "index" /* suffix for index */
+#define MIXSTATUS "status" /* suffix for status */
+#define MIXSORTCACHE "sortcache"/* suffix for sortcache */
+#define METAMAX (MEGABYTE-1) /* maximum metadata file size (sanity check) */
+
+
+/* MIX file formats */
+
+ /* sequence format (all but msg files) */
+#define SEQFMT "S%08lx\015\012"
+ /* metadata file format */
+#define MTAFMT "V%08lx\015\012L%08lx\015\012N%08lx\015\012"
+ /* index file record format */
+#define IXRFMT ":%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:%08lx:%08lx:%08lx:%08lx:\015\012"
+ /* status file record format */
+#define STRFMT ":%08lx:%08lx:%04x:%08lx:\015\012"
+ /* message file header format */
+#define MSRFMT "%s%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:\015\012"
+#define MSGTOK ":msg:"
+#define MSGTSZ (sizeof(MSGTOK)-1)
+ /* sortcache file record format */
+#define SCRFMT ":%08lx:%08lx:%08lx:%08lx:%08lx:%c%08lx:%08lx:%08lx:\015\012"
+
+/* MIX I/O stream local data */
+
+typedef struct mix_local {
+ unsigned long curmsg; /* current message file number */
+ unsigned long newmsg; /* current new message file number */
+ time_t lastsnarf; /* last snarf time */
+ int msgfd; /* file description of current msg file */
+ int mfd; /* file descriptor of open metadata */
+ unsigned long metaseq; /* metadata sequence */
+ char *index; /* mailbox index name */
+ unsigned long indexseq; /* index sequence */
+ char *status; /* mailbox status name */
+ unsigned long statusseq; /* status sequence */
+ char *sortcache; /* mailbox sortcache name */
+ unsigned long sortcacheseq; /* sortcache sequence */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned int expok : 1; /* non-zero if expunge reports OK */
+ unsigned int internal : 1; /* internally opened, do not validate */
+} MIXLOCAL;
+
+
+#define MIXBURP struct mix_burp
+
+MIXBURP {
+ unsigned long fileno; /* message file number */
+ char *name; /* message file name */
+ SEARCHSET *tail; /* tail of ranges */
+ SEARCHSET set; /* set of retained ranges */
+ MIXBURP *next; /* next file to burp */
+};
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MIXLOCAL *) stream->local)
+
+/* Function prototypes */
+
+DRIVER *mix_valid (char *name);
+long mix_isvalid (char *name,char *meta);
+void *mix_parameters (long function,void *value);
+long mix_dirfmttest (char *name);
+void mix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+long mix_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz);
+void mix_list (MAILSTREAM *stream,char *ref,char *pat);
+void mix_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mix_subscribe (MAILSTREAM *stream,char *mailbox);
+long mix_unsubscribe (MAILSTREAM *stream,char *mailbox);
+long mix_create (MAILSTREAM *stream,char *mailbox);
+long mix_delete (MAILSTREAM *stream,char *mailbox);
+long mix_rename (MAILSTREAM *stream,char *old,char *newname);
+int mix_rselect (struct direct *name);
+MAILSTREAM *mix_open (MAILSTREAM *stream);
+void mix_close (MAILSTREAM *stream,long options);
+void mix_abort (MAILSTREAM *stream);
+char *mix_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags);
+long mix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void mix_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+unsigned long *mix_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
+ SORTPGM *pgm,long flags);
+THREADNODE *mix_thread (MAILSTREAM *stream,char *type,char *charset,
+ SEARCHPGM *spg,long flags);
+long mix_ping (MAILSTREAM *stream);
+void mix_check (MAILSTREAM *stream);
+long mix_expunge (MAILSTREAM *stream,char *sequence,long options);
+int mix_select (struct direct *name);
+int mix_msgfsort (const void *d1,const void *d2);
+long mix_addset (SEARCHSET **set,unsigned long start,unsigned long size);
+long mix_burp (MAILSTREAM *stream,MIXBURP *burp,unsigned long *reclaimed);
+long mix_burp_check (SEARCHSET *set,size_t size,char *file);
+long mix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,
+ long options);
+long mix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+long mix_append_msg (MAILSTREAM *stream,FILE *f,char *flags,MESSAGECACHE *delt,
+ STRING *msg,SEARCHSET *set,unsigned long seq);
+
+FILE *mix_parse (MAILSTREAM *stream,FILE **idxf,long iflags,long sflags);
+char *mix_meta_slurp (MAILSTREAM *stream,unsigned long *seq);
+long mix_meta_update (MAILSTREAM *stream);
+long mix_index_update (MAILSTREAM *stream,FILE *idxf,long flag);
+long mix_status_update (MAILSTREAM *stream,FILE *statf,long flag);
+FILE *mix_data_open (MAILSTREAM *stream,int *fd,long *size,
+ unsigned long newsize);
+FILE *mix_sortcache_open (MAILSTREAM *stream);
+long mix_sortcache_update (MAILSTREAM *stream,FILE **sortcache);
+char *mix_read_record (FILE *f,char *buf,unsigned long buflen,char *type);
+unsigned long mix_read_sequence (FILE *f);
+char *mix_dir (char *dst,char *name);
+char *mix_file (char *dst,char *dir,char *name);
+char *mix_file_data (char *dst,char *dir,unsigned long data);
+unsigned long mix_modseq (unsigned long oldseq);
+
+/* MIX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mixdriver = {
+ "mix", /* driver name */
+ /* driver flags */
+ DR_MAIL|DR_LOCAL|DR_NOFAST|DR_CRLF|DR_LOCKING|DR_DIRFMT|DR_MODSEQ,
+ (DRIVER *) NIL, /* next driver */
+ mix_valid, /* mailbox is valid for us */
+ mix_parameters, /* manipulate parameters */
+ mix_scan, /* scan mailboxes */
+ mix_list, /* find mailboxes */
+ mix_lsub, /* find subscribed mailboxes */
+ mix_subscribe, /* subscribe to mailbox */
+ mix_unsubscribe, /* unsubscribe from mailbox */
+ mix_create, /* create mailbox */
+ mix_delete, /* delete mailbox */
+ mix_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ mix_open, /* open mailbox */
+ mix_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mix_header, /* fetch message header only */
+ mix_text, /* fetch message body only */
+ NIL, /* fetch partial message test */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ mix_flag, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ mix_sort, /* sort messages */
+ mix_thread, /* thread messages */
+ mix_ping, /* ping mailbox to see if still alive */
+ mix_check, /* check for new messages */
+ mix_expunge, /* expunge deleted messages */
+ mix_copy, /* copy messages to another mailbox */
+ mix_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mixproto = {&mixdriver};
+
+/* MIX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mix_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return mix_isvalid (name,tmp) ? &mixdriver : NIL;
+}
+
+
+/* MIX mail test for valid mailbox
+ * Accepts: mailbox name
+ * buffer to return meta name
+ * Returns: T if valid, NIL otherwise, metadata name written in both cases
+ */
+
+long mix_isvalid (char *name,char *meta)
+{
+ char dir[MAILTMPLEN];
+ struct stat sbuf;
+ /* validate name as directory */
+ if (!(errno = ((strlen (name) > NETMAXMBX) ? ENAMETOOLONG : NIL)) &&
+ *mix_dir (dir,name) && mix_file (meta,dir,MIXMETA) &&
+ !stat (dir,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
+ /* name is directory; is it mix? */
+ if (!stat (meta,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG))
+ return LONGT;
+ else errno = NIL; /* directory but not mix */
+ }
+ return NIL;
+}
+
+/* MIX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mix_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = mailboxfile ((char *) value,"~/INBOX");
+ break;
+ case GET_DIRFMTTEST:
+ ret = (void *) mix_dirfmttest;
+ break;
+ case GET_SCANCONTENTS:
+ ret = (void *) mix_scan_contents;
+ break;
+ case SET_ONETIMEEXPUNGEATPING:
+ if (value) ((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T;
+ case GET_ONETIMEEXPUNGEATPING:
+ if (value) ret = (void *)
+ (((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL);
+ break;
+ }
+ return ret;
+}
+
+
+/* MIX test for directory format internal node
+ * Accepts: candidate node name
+ * Returns: T if internal name, NIL otherwise
+ */
+
+long mix_dirfmttest (char *name)
+{
+ /* belongs to MIX if starts with .mix */
+ return strncmp (name,MIXNAME,sizeof (MIXNAME) - 1) ? NIL : LONGT;
+}
+
+/* MIX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MIX scan mailbox for contents
+ * Accepts: mailbox name
+ * desired contents
+ * contents size
+ * file size (ignored)
+ * Returns: NIL if contents not found, T if found
+ */
+
+long mix_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz)
+{
+ long i,nfiles;
+ void *a;
+ char *s;
+ long ret = NIL;
+ size_t namelen = strlen (name);
+ struct stat sbuf;
+ struct direct **names = NIL;
+ if ((nfiles = scandir (name,&names,mix_select,mix_msgfsort)) > 0)
+ for (i = 0; i < nfiles; ++i) {
+ if (!ret) {
+ sprintf (s = (char *) fs_get (namelen + strlen (names[i]->d_name) + 2),
+ "%s/%s",name,names[i]->d_name);
+ if (!stat (s,&sbuf) && (csiz <= sbuf.st_size))
+ ret = dummy_scan_contents (s,contents,csiz,sbuf.st_size);
+ fs_give ((void **) &s);
+ }
+ fs_give ((void **) &names[i]);
+ }
+ /* free directory list */
+ if (a = (void *) names) fs_give ((void **) &a);
+ return ret;
+}
+
+/* MIX list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mix_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MIX list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mix_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MIX mail subscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to add to subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long mix_subscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return sm_subscribe (mailbox);
+}
+
+
+/* MIX mail unsubscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to delete from subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long mix_unsubscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return sm_unsubscribe (mailbox);
+}
+
+/* MIX mail create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mix_create (MAILSTREAM *stream,char *mailbox)
+{
+ DRIVER *test;
+ FILE *f;
+ int c,i;
+ char *t,tmp[MAILTMPLEN],file[MAILTMPLEN];
+ char *s = strrchr (mailbox,'/');
+ unsigned long now = time (NIL);
+ long ret = NIL;
+ /* always create \NoSelect if trailing / */
+ if (s && !s[1]) return dummy_create (stream,mailbox);
+ /* validate name */
+ if (mix_dirfmttest (s ? s + 1 : mailbox))
+ sprintf(tmp,"Can't create mailbox %.80s: invalid MIX-format name",mailbox);
+ /* must not already exist */
+ else if ((test = mail_valid (NIL,mailbox,NIL)) &&
+ strcmp (test->name,"dummy"))
+ sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox);
+ /* create directory and metadata */
+ else if (!dummy_create_path (stream,
+ mix_file (file,mix_dir (tmp,mailbox),MIXMETA),
+ get_dir_protection (mailbox)))
+ sprintf (tmp,"Can't create mailbox %.80s: %.80s",mailbox,strerror (errno));
+ else if (!(f = fopen (file,"w")))
+ sprintf (tmp,"Can't re-open metadata %.80s: %.80s",mailbox,
+ strerror (errno));
+ else { /* success, write initial metadata */
+ fprintf (f,SEQFMT,now);
+ fprintf (f,MTAFMT,now,0,now);
+ for (i = 0, c = 'K'; (i < NUSERFLAGS) &&
+ (t = (stream && stream->user_flags[i]) ? stream->user_flags[i] :
+ default_user_flag (i)) && *t; ++i) {
+ putc (c,f); /* write another keyword */
+ fputs (t,f);
+ c = ' '; /* delimiter is now space */
+ }
+ fclose (f);
+ set_mbx_protections (mailbox,file);
+ /* point to suffix */
+ s = file + strlen (file) - (sizeof (MIXMETA) - 1);
+ strcpy (s,MIXINDEX); /* create index */
+ if (!dummy_create_path (stream,file,get_dir_protection (mailbox)))
+ sprintf (tmp,"Can't create mix mailbox index: %.80s",strerror (errno));
+ else {
+ set_mbx_protections (mailbox,file);
+ strcpy (s,MIXSTATUS); /* create status */
+ if (!dummy_create_path (stream,file,get_dir_protection (mailbox)))
+ sprintf (tmp,"Can't create mix mailbox status: %.80s",
+ strerror (errno));
+ else {
+ set_mbx_protections (mailbox,file);
+ sprintf (s,"%08lx",now);/* message file */
+ if (!dummy_create_path (stream,file,get_dir_protection (mailbox)))
+ sprintf (tmp,"Can't create mix mailbox data: %.80s",
+ strerror (errno));
+ else {
+ set_mbx_protections (mailbox,file);
+ ret = LONGT; /* declare success at this point */
+ }
+ }
+ }
+ }
+ if (!ret) MM_LOG (tmp,ERROR); /* some error */
+ return ret;
+}
+
+/* MIX mail delete mailbox
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mix_delete (MAILSTREAM *stream,char *mailbox)
+{
+ DIR *dirp;
+ struct direct *d;
+ int fd = -1;
+ char *s,tmp[MAILTMPLEN];
+ if (!mix_isvalid (mailbox,tmp))
+ sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox);
+ else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB))
+ sprintf (tmp,"Can't lock mailbox for delete: %.80s",mailbox);
+ /* delete metadata */
+ else if (unlink (tmp)) sprintf (tmp,"Can't delete mailbox %.80s index: %80s",
+ mailbox,strerror (errno));
+ else {
+ close (fd); /* close descriptor on deleted metadata */
+ /* get directory name */
+ *(s = strrchr (tmp,'/')) = '\0';
+ if (dirp = opendir (tmp)) { /* open directory */
+ *s++ = '/'; /* restore delimiter */
+ /* massacre messages */
+ while (d = readdir (dirp)) if (mix_dirfmttest (d->d_name)) {
+ strcpy (s,d->d_name); /* make path */
+ unlink (tmp); /* sayonara */
+ }
+ closedir (dirp); /* flush directory */
+ *(s = strrchr (tmp,'/')) = '\0';
+ if (rmdir (tmp)) { /* try to remove the directory */
+ sprintf (tmp,"Can't delete name %.80s: %.80s",
+ mailbox,strerror (errno));
+ MM_LOG (tmp,WARN);
+ }
+ }
+ return T; /* always success */
+ }
+ if (fd >= 0) close (fd); /* close any descriptor on metadata */
+ MM_LOG (tmp,ERROR); /* something failed */
+ return NIL;
+}
+
+/* MIX mail rename mailbox
+ * Accepts: MIX mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long mix_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN];
+ struct stat sbuf;
+ int fd = -1;
+ if (!mix_isvalid (old,tmp))
+ sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old);
+ else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB))
+ sprintf (tmp,"Can't lock mailbox for rename: %.80s",old);
+ else if (mix_dirfmttest ((s = strrchr (newname,'/')) ? s + 1 : newname))
+ sprintf (tmp,"Can't rename to mailbox %.80s: invalid MIX-format name",
+ newname);
+ /* new mailbox name must not be valid */
+ else if (mix_isvalid (newname,tmp))
+ sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists",
+ newname);
+ else {
+ mix_dir (tmp,old); /* build old directory name */
+ mix_dir (tmp1,newname); /* and new directory name */
+ /* easy if not INBOX */
+ if (compare_cstring (old,"INBOX")) {
+ /* found superior to destination name? */
+ if (s = strrchr (tmp1,'/')) {
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp1,get_dir_protection (newname)))
+ return NIL;
+ *s = c; /* restore full name */
+ }
+ if (!rename (tmp,tmp1)) {
+ close (fd); /* close descriptor on metadata */
+ return LONGT;
+ }
+ }
+
+ /* RFC 3501 requires this */
+ else if (dummy_create_path (stream,strcat (tmp1,"/"),
+ get_dir_protection (newname))) {
+ void *a;
+ int i,n,lasterror;
+ char *src,*dst;
+ struct direct **names = NIL;
+ size_t srcl = strlen (tmp);
+ size_t dstl = strlen (tmp1);
+ /* rename each mix file to new directory */
+ for (i = lasterror = 0,n = scandir (tmp,&names,mix_rselect,alphasort);
+ i < n; ++i) {
+ size_t len = strlen (names[i]->d_name);
+ sprintf (src = (char *) fs_get (srcl + len + 2),"%s/%s",
+ tmp,names[i]->d_name);
+ sprintf (dst = (char *) fs_get (dstl + len + 1),"%s%s",
+ tmp1,names[i]->d_name);
+ if (rename (src,dst)) lasterror = errno;
+ fs_give ((void **) &src);
+ fs_give ((void **) &dst);
+ fs_give ((void **) &names[i]);
+ }
+ /* free directory list */
+ if (a = (void *) names) fs_give ((void **) &a);
+ if (lasterror) errno = lasterror;
+ else {
+ close (fd); /* close descriptor on metadata */
+ return mix_create (NIL,"INBOX");
+ }
+ }
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",
+ old,newname,strerror (errno));
+ }
+ if (fd >= 0) close (fd); /* close any descriptor on metadata */
+ MM_LOG (tmp,ERROR); /* something failed */
+ return NIL;
+}
+
+
+/* MIX test for mix name
+ * Accepts: candidate directory name
+ * Returns: T if mix file name, NIL otherwise
+ */
+
+int mix_rselect (struct direct *name)
+{
+ return mix_dirfmttest (name->d_name);
+}
+
+/* MIX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mix_open (MAILSTREAM *stream)
+{
+ short silent;
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&mixproto);
+ if (stream->local) fatal ("mix recycle stream");
+ stream->local = memset (fs_get (sizeof (MIXLOCAL)),0,sizeof (MIXLOCAL));
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ /* make temporary buffer */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ /* set stream->mailbox to be directory name */
+ mix_dir (LOCAL->buf,stream->mailbox);
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (LOCAL->buf);
+ LOCAL->msgfd = -1; /* currently no file open */
+ if (!(((!stream->rdonly && /* open metadata file */
+ ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA),
+ O_RDWR,NIL)) >= 0)) ||
+ ((stream->rdonly = T) &&
+ ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA),
+ O_RDONLY,NIL)) >= 0))) &&
+ !flock (LOCAL->mfd,LOCK_SH))) {
+ MM_LOG ("Error opening mix metadata file",ERROR);
+ mix_abort (stream);
+ stream = NIL; /* open fails */
+ }
+ else { /* metadata open, complete open */
+ LOCAL->index = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXINDEX));
+ LOCAL->status = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXSTATUS));
+ LOCAL->sortcache = cpystr (mix_file (LOCAL->buf,stream->mailbox,
+ MIXSORTCACHE));
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (silent = stream->silent) LOCAL->internal = T;
+ stream->silent = T;
+ if (mix_ping (stream)) { /* do initial ping */
+ /* try burping in case we are exclusive */
+ if (!stream->rdonly) mix_expunge (stream,"",NIL);
+ if (!(stream->nmsgs || stream->silent))
+ MM_LOG ("Mailbox is empty",(long) NIL);
+ stream->silent = silent; /* now notify upper level */
+ mail_exists (stream,stream->nmsgs);
+ stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
+ stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ stream->kwd_create = /* can we create new user flags? */
+ (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ? NIL : T;
+ }
+ else { /* got murdelyzed in ping */
+ mix_abort (stream);
+ stream = NIL;
+ }
+ }
+ return stream; /* return stream to caller */
+}
+
+/* MIX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mix_close (MAILSTREAM *stream,long options)
+{
+ if (LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ /* burp-only or expunge */
+ mix_expunge (stream,(options & CL_EXPUNGE) ? NIL : "",NIL);
+ mix_abort (stream);
+ stream->silent = silent; /* reset silent state */
+ }
+}
+
+
+/* MIX mail abort stream
+ * Accepts: MAIL stream
+ */
+
+void mix_abort (MAILSTREAM *stream)
+{
+ if (LOCAL) { /* only if a file is open */
+ /* close current message file if open */
+ if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
+ /* close current metadata file if open */
+ if (LOCAL->mfd >= 0) close (LOCAL->mfd);
+ if (LOCAL->index) fs_give ((void **) &LOCAL->index);
+ if (LOCAL->status) fs_give ((void **) &LOCAL->status);
+ if (LOCAL->sortcache) fs_give ((void **) &LOCAL->sortcache);
+ /* free local scratch buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* MIX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mix_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ unsigned long i,j,k;
+ int fd;
+ char *s,tmp[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ if (length) *length = 0; /* default return */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get elt */
+ /* is message in current message file? */
+ if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) {
+ if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
+ if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox,
+ elt->private.spare.data),
+ O_RDONLY,NIL)) < 0) return "";
+ /* got file */
+ LOCAL->curmsg = elt->private.spare.data;
+ }
+ lseek (LOCAL->msgfd,elt->private.special.offset,L_SET);
+ /* size of special data and header */
+ j = elt->private.msg.header.offset + elt->private.msg.header.text.size;
+ if (j > LOCAL->buflen) { /* is buffer big enough? */
+ /* no, make one that is */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = j) + 1);
+ }
+ /* Maybe someday validate internaldate too */
+ /* slurp special data + header, validate */
+ if ((read (LOCAL->msgfd,LOCAL->buf,j) == j) &&
+ !strncmp (LOCAL->buf,MSGTOK,MSGTSZ) &&
+ (elt->private.uid == strtoul ((char *) LOCAL->buf + MSGTSZ,&s,16)) &&
+ (*s++ == ':') && (s = strchr (s,':')) &&
+ (k = strtoul (s+1,&s,16)) && (*s++ == ':') &&
+ (s < (char *) (LOCAL->buf + elt->private.msg.header.offset))) {
+ /* won, set offset and size of message */
+ i = elt->private.msg.header.offset;
+ *length = elt->private.msg.header.text.size;
+ if (k != elt->rfc822_size) {
+ sprintf (tmp,"Inconsistency in mix message size, uid=%lx (%lu != %lu)",
+ elt->private.uid,elt->rfc822_size,k);
+ MM_LOG (tmp,WARN);
+ }
+ }
+ else { /* document the problem */
+ LOCAL->buf[100] = '\0'; /* tie off buffer at no more than 100 octets */
+ /* or at newline, whichever is first */
+ if (s = strpbrk (LOCAL->buf,"\015\012")) *s = '\0';
+ sprintf (tmp,"Error reading mix message header, uid=%lx, s=%.0lx, h=%s",
+ elt->private.uid,elt->rfc822_size,LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ *length = i = j = 0; /* default to empty */
+ }
+ LOCAL->buf[j] = '\0'; /* tie off buffer at the end */
+ return (char *) LOCAL->buf + i;
+}
+
+/* MIX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ unsigned long i;
+ FDDATA d;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);
+ /* is message in current message file? */
+ if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) {
+ if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
+ if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox,
+ elt->private.spare.data),
+ O_RDONLY,NIL)) < 0) return NIL;
+ /* got file */
+ LOCAL->curmsg = elt->private.spare.data;
+ }
+ /* doing non-peek fetch? */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ FILE *idxf; /* yes, process metadata/index/status */
+ FILE *statf = mix_parse (stream,&idxf,NIL,LONGT);
+ elt->seen = T; /* mark as seen */
+ MM_FLAGS (stream,elt->msgno);
+ /* update status file if possible */
+ if (statf && !stream->rdonly) {
+ elt->private.mod = LOCAL->statusseq = mix_modseq (LOCAL->statusseq);
+ mix_status_update (stream,statf,NIL);
+ }
+ if (idxf) fclose (idxf); /* release index and status file */
+ if (statf) fclose (statf);
+ }
+ d.fd = LOCAL->msgfd; /* set up file descriptor */
+ /* offset of message text */
+ d.pos = elt->private.special.offset + elt->private.msg.header.offset +
+ elt->private.msg.header.text.size;
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* chunk size */
+ INIT (bs,fd_string,&d,elt->rfc822_size - elt->private.msg.header.text.size);
+ return T;
+}
+
+/* MIX mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ */
+
+void mix_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned long i,uf,ffkey;
+ long f;
+ short nf;
+ FILE *idxf;
+ FILE *statf = mix_parse (stream,&idxf,NIL,LONGT);
+ unsigned long seq = mix_modseq (LOCAL->statusseq);
+ /* find first free key */
+ for (ffkey = 0; (ffkey < NUSERFLAGS) && stream->user_flags[ffkey]; ++ffkey);
+ /* parse sequence and flags */
+ if (((flags & ST_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) &&
+ ((f = mail_parse_flags (stream,flag,&uf)) || uf)) {
+ /* alter flags */
+ for (i = 1,nf = (flags & ST_SET) ? T : NIL; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted;
+ old.flagged = elt->flagged; old.answered = elt->answered;
+ old.draft = elt->draft; old.user_flags = elt->user_flags;
+ if (f&fSEEN) elt->seen = nf;
+ if (f&fDELETED) elt->deleted = nf;
+ if (f&fFLAGGED) elt->flagged = nf;
+ if (f&fANSWERED) elt->answered = nf;
+ if (f&fDRAFT) elt->draft = nf;
+ /* user flags */
+ if (flags & ST_SET) elt->user_flags |= uf;
+ else elt->user_flags &= ~uf;
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) ||
+ (old.answered != elt->answered) || (old.draft != elt->draft) ||
+ (old.user_flags != elt->user_flags)) {
+ if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq;
+ MM_FLAGS (stream,elt->msgno);
+ }
+ }
+ /* update status file after change */
+ if (statf && (seq == LOCAL->statusseq))
+ mix_status_update (stream,statf,NIL);
+ /* update metadata if created a keyword */
+ if ((ffkey < NUSERFLAGS) && stream->user_flags[ffkey] &&
+ !mix_meta_update (stream))
+ MM_LOG ("Error updating mix metadata after keyword creation",ERROR);
+ }
+ if (statf) fclose (statf); /* release status file if still open */
+ if (idxf) fclose (idxf); /* release index file */
+}
+
+/* MIX mail sort messages
+ * Accepts: mail stream
+ * character set
+ * search program
+ * sort program
+ * option flags
+ * Returns: vector of sorted message sequences or NIL if error
+ */
+
+unsigned long *mix_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
+ SORTPGM *pgm,long flags)
+{
+ unsigned long *ret;
+ FILE *sortcache = mix_sortcache_open (stream);
+ ret = mail_sort_msgs (stream,charset,spg,pgm,flags);
+ mix_sortcache_update (stream,&sortcache);
+ return ret;
+}
+
+
+/* MIX mail thread messages
+ * Accepts: mail stream
+ * thread type
+ * character set
+ * search program
+ * option flags
+ * Returns: thread node tree or NIL if error
+ */
+
+THREADNODE *mix_thread (MAILSTREAM *stream,char *type,char *charset,
+ SEARCHPGM *spg,long flags)
+{
+ THREADNODE *ret;
+ FILE *sortcache = mix_sortcache_open (stream);
+ ret = mail_thread_msgs (stream,type,charset,spg,flags,mail_sort_msgs);
+ mix_sortcache_update (stream,&sortcache);
+ return ret;
+}
+
+/* MIX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+static int snarfing = 0; /* lock against recursive snarfing */
+
+long mix_ping (MAILSTREAM *stream)
+{
+ FILE *idxf,*statf;
+ struct stat sbuf;
+ STRING msg;
+ MESSAGECACHE *elt;
+ int mfd,ifd,sfd;
+ unsigned long i,msglen;
+ char *message,date[MAILTMPLEN],flags[MAILTMPLEN];
+ MAILSTREAM *sysibx = NIL;
+ long ret = NIL;
+ long snarfok = LONGT;
+ /* time to snarf? */
+ if (stream->inbox && !stream->rdonly && !snarfing &&
+ (time (0) >= (LOCAL->lastsnarf +
+ (time_t) mail_parameters (NIL,GET_SNARFINTERVAL,NIL)))) {
+ appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
+ copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
+ MM_CRITICAL (stream); /* go critical */
+ snarfing = T; /* don't recursively snarf */
+ /* disable APPENDUID/COPYUID callbacks */
+ mail_parameters (NIL,SET_APPENDUID,NIL);
+ mail_parameters (NIL,SET_COPYUID,NIL);
+ /* sizes match and anything in sysinbox? */
+ if (!stat (sysinbox (),&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG) &&
+ sbuf.st_size && (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
+ !sysibx->rdonly && sysibx->nmsgs) {
+ /* for each message in sysibx mailbox */
+ for (i = 1; snarfok && (i <= sysibx->nmsgs); ++i)
+ if (!(elt = mail_elt (sysibx,i))->deleted &&
+ (message = mail_fetch_message (sysibx,i,&msglen,FT_PEEK)) &&
+ msglen) {
+ mail_date (date,elt); /* make internal date string */
+ /* make flag string */
+ flags[0] = flags[1] = '\0';
+ if (elt->seen) strcat (flags," \\Seen");
+ if (elt->flagged) strcat (flags," \\Flagged");
+ if (elt->answered) strcat (flags," \\Answered");
+ if (elt->draft) strcat (flags," \\Draft");
+ flags[0] = '(';
+ strcat (flags,")");
+ INIT (&msg,mail_string,message,msglen);
+ if (snarfok = mail_append_full (stream,"INBOX",flags,date,&msg)) {
+ char sequence[15];
+ sprintf (sequence,"%lu",i);
+ mail_flag (sysibx,sequence,"\\Deleted",ST_SET);
+ }
+ }
+
+ /* now expunge all those messages */
+ if (snarfok) mail_expunge (sysibx);
+ else {
+ sprintf (LOCAL->buf,"Can't copy new mail at message: %lu",i - 1);
+ MM_LOG (LOCAL->buf,WARN);
+ }
+ }
+ if (sysibx) mail_close (sysibx);
+ /* reenable APPENDUID/COPYUID */
+ mail_parameters (NIL,SET_APPENDUID,(void *) au);
+ mail_parameters (NIL,SET_COPYUID,(void *) cu);
+ snarfing = NIL; /* no longer snarfing */
+ MM_NOCRITICAL (stream); /* release critical */
+ LOCAL->lastsnarf = time (0);/* note time of last snarf */
+ }
+ /* expunging OK if global flag set */
+ if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T;
+ /* process metadata/index/status */
+ if (statf = mix_parse (stream,&idxf,LONGT,
+ (LOCAL->internal ? NIL : LONGT))) {
+ fclose (statf); /* just close the status file */
+ ret = LONGT; /* declare success */
+ }
+ if (idxf) fclose (idxf); /* release index file */
+ LOCAL->expok = NIL; /* expunge no longer OK */
+ if (!ret) mix_abort (stream); /* murdelyze stream if ping fails */
+ return ret;
+}
+
+
+/* MIX mail checkpoint mailbox (burp only)
+ * Accepts: MAIL stream
+ */
+
+void mix_check (MAILSTREAM *stream)
+{
+ if (stream->rdonly) /* won't do on readonly files! */
+ MM_LOG ("Checkpoint ignored on readonly mailbox",NIL);
+ /* do burp-only expunge action */
+ if (mix_expunge (stream,"",NIL)) MM_LOG ("Check completed",(long) NIL);
+}
+
+/* MIX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL, empty string for burp only
+ * expunge options
+ * Returns: T on success, NIL if failure
+ */
+
+long mix_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ FILE *idxf = NIL;
+ FILE *statf = NIL;
+ MESSAGECACHE *elt;
+ int ifd,sfd;
+ long ret;
+ unsigned long i;
+ unsigned long nexp = 0;
+ unsigned long reclaimed = 0;
+ int burponly = (sequence && !*sequence);
+ LOCAL->expok = T; /* expunge during ping is OK */
+ if (!(ret = burponly || !sequence ||
+ ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) || stream->rdonly);
+ /* read index and open status exclusive */
+ else if (statf = mix_parse (stream,&idxf,LONGT,
+ LOCAL->internal ? NIL : LONGT)) {
+ /* expunge unless just burping */
+ if (!burponly) for (i = 1; i <= stream->nmsgs;) {
+ elt = mail_elt (stream,i);/* need to expunge this message? */
+ if (sequence ? elt->sequence : elt->deleted) {
+ ++nexp; /* yes, make it so */
+ mail_expunged (stream,i);
+ }
+ else ++i; /* otherwise advance to next message */
+ }
+
+ /* burp if can get exclusive access */
+ if (!flock (LOCAL->mfd,LOCK_EX|LOCK_NB)) {
+ void *a;
+ struct direct **names = NIL;
+ long nfiles = scandir (stream->mailbox,&names,mix_select,mix_msgfsort);
+ if (nfiles > 0) { /* if have message files */
+ MIXBURP *burp,*cur;
+ /* initialize burp list */
+ for (i = 0, burp = cur = NIL; i < nfiles; ++i) {
+ MIXBURP *nxt = (MIXBURP *) memset (fs_get (sizeof (MIXBURP)),0,
+ sizeof (MIXBURP));
+ /* another file found */
+ if (cur) cur = cur->next = nxt;
+ else cur = burp = nxt;
+ cur->name = names[i]->d_name;
+ cur->fileno = strtoul (cur->name + sizeof (MIXNAME) - 1,NIL,16);
+ cur->tail = &cur->set;
+ fs_give ((void **) &names[i]);
+ }
+ /* now load ranges */
+ for (i = 1, cur = burp; ret && (i <= stream->nmsgs); i++) {
+ /* is this message in current set? */
+ elt = mail_elt (stream,i);
+ if (cur && (elt->private.spare.data != cur->fileno)) {
+ /* restart if necessary */
+ if (elt->private.spare.data < cur->fileno) cur = burp;
+ /* hunt for appropriate mailbox */
+ while (cur && (elt->private.spare.data > cur->fileno))
+ cur = cur->next;
+ /* ought to have found it now... */
+ if (cur && (elt->private.spare.data != cur->fileno)) cur = NIL;
+ }
+ /* if found, add to set */
+ if (cur) ret = mix_addset (&cur->tail,elt->private.special.offset,
+ elt->private.msg.header.offset +
+ elt->rfc822_size);
+ else { /* uh-oh */
+ sprintf (LOCAL->buf,"Can't locate mix message file %.08lx",
+ elt->private.spare.data);
+ MM_LOG (LOCAL->buf,ERROR);
+ ret = NIL;
+ }
+ }
+ if (ret) /* if no errors, burp all files */
+ for (cur = burp; ret && cur; cur = cur->next) {
+ /* if non-empty, burp it */
+ if (cur->set.last) ret = mix_burp (stream,cur,&reclaimed);
+ /* empty, delete it unless new msg file */
+ else if (mix_file_data (LOCAL->buf,stream->mailbox,cur->fileno) &&
+ ((cur->fileno == LOCAL->newmsg) ?
+ truncate (LOCAL->buf,0) : unlink (LOCAL->buf))) {
+ sprintf (LOCAL->buf,
+ "Can't delete empty message file %.80s: %.80s",
+ cur->name,strerror (errno));
+ MM_LOG (LOCAL->buf,WARN);
+ }
+ }
+ }
+ else MM_LOG ("No mix message files found during expunge",WARN);
+ /* free directory list */
+ if (a = (void *) names) fs_give ((void **) &a);
+ }
+
+ /* either way, re-acquire shared lock */
+ if (flock (LOCAL->mfd,LOCK_SH|LOCK_NB))
+ fatal ("Unable to re-acquire metadata shared lock!");
+ /* Do this step even if ret is NIL (meaning some burp problem)! */
+ if (nexp || reclaimed) { /* rewrite index and status if changed */
+ LOCAL->indexseq = mix_modseq (LOCAL->indexseq);
+ if (mix_index_update (stream,idxf,NIL)) {
+ LOCAL->statusseq = mix_modseq (LOCAL->statusseq);
+ /* set failure if update fails */
+ ret = mix_status_update (stream,statf,NIL);
+ }
+ }
+ }
+ if (statf) fclose (statf); /* close status if still open */
+ if (idxf) fclose (idxf); /* close index if still open */
+ LOCAL->expok = NIL; /* cancel expok */
+ if (ret) { /* only if success */
+ char *s = NIL;
+ if (nexp) sprintf (s = LOCAL->buf,"Expunged %lu messages",nexp);
+ else if (reclaimed)
+ sprintf (s=LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed);
+ else if (!burponly)
+ s = stream->rdonly ? "Expunge ignored on readonly mailbox" :
+ "No messages deleted, so no update needed";
+ if (s) MM_LOG (s,(long) NIL);
+ }
+ return ret;
+}
+
+/* MIX test for message file name
+ * Accepts: candidate directory name
+ * Returns: T if message file name, NIL otherwise
+ *
+ * ".mix" with no suffix was used by experimental versions
+ */
+
+int mix_select (struct direct *name)
+{
+ char c,*s;
+ /* make sure name has prefix */
+ if (mix_dirfmttest (name->d_name)) {
+ for (c = *(s = name->d_name + sizeof (MIXNAME) - 1); c && isxdigit (c);
+ c = *s++);
+ if (!c) return T; /* all-hex or no suffix */
+ }
+ return NIL; /* not suffix or non-hex */
+}
+
+
+/* MIX msg file name comparision
+ * Accepts: first candidate directory entry
+ * second candidate directory entry
+ * Returns: -1 if d1 < d2, 0 if d1 == d2, 1 d1 > d2
+ */
+
+int mix_msgfsort (const void *d1,const void *d2)
+{
+ char *n1 = (*(struct direct **) d1)->d_name + sizeof (MIXNAME) - 1;
+ char *n2 = (*(struct direct **) d2)->d_name + sizeof (MIXNAME) - 1;
+ return compare_ulong (*n1 ? strtoul (n1,NIL,16) : 0,
+ *n2 ? strtoul (n2,NIL,16) : 0);
+}
+
+
+/* MIX add a range to a set
+ * Accepts: pointer to set to add
+ * start of set
+ * size of set
+ * Returns: T if success, set updated, NIL otherwise
+ */
+
+long mix_addset (SEARCHSET **set,unsigned long start,unsigned long size)
+{
+ SEARCHSET *s = *set;
+ if (start < s->last) { /* sanity check */
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Backwards-running mix index %lu < %lu",start,s->last);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* range initially empty? */
+ if (!s->last) s->first = start;
+ else if (start > s->last) /* no, start new range if can't append */
+ (*set = s = s->next = mail_newsearchset ())->first = start;
+ s->last = start + size; /* end of current range */
+ return LONGT;
+}
+
+/* MIX burp message file
+ * Accepts: MAIL stream
+ * current burp block for this message
+ * Returns: T if successful, NIL if failed
+ */
+
+static char *staterr = "Error in stat of mix message file %.80s: %.80s";
+static char *truncerr = "Error truncating mix message file %.80s: %.80s";
+
+long mix_burp (MAILSTREAM *stream,MIXBURP *burp,unsigned long *reclaimed)
+{
+ MESSAGECACHE *elt;
+ SEARCHSET *set;
+ struct stat sbuf;
+ off_t rpos,wpos;
+ size_t size,wsize,wpending,written;
+ int fd;
+ FILE *f;
+ void *s;
+ unsigned long i;
+ long ret = NIL;
+ /* build file name */
+ mix_file_data (LOCAL->buf,stream->mailbox,burp->fileno);
+ /* need to burp at start or multiple ranges? */
+ if (!burp->set.first && !burp->set.next) {
+ /* easy case, single range at start of file */
+ if (stat (LOCAL->buf,&sbuf)) {
+ sprintf (LOCAL->buf,staterr,burp->name,strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ }
+ /* is this range sane? */
+ else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) {
+ /* if matches range then no burp needed! */
+ if (burp->set.last == sbuf.st_size) ret = LONGT;
+ /* just need to remove cruft at end */
+ else if (ret = !truncate (LOCAL->buf,burp->set.last))
+ *reclaimed += sbuf.st_size - burp->set.last;
+ else {
+ sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ }
+ }
+ }
+ /* have to do more work, get the file */
+ else if (((fd = open (LOCAL->buf,O_RDWR,NIL)) < 0) ||
+ !(f = fdopen (fd,"r+b"))) {
+ sprintf (LOCAL->buf,"Error opening mix message file %.80s: %.80s",
+ burp->name,strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ if (fd >= 0) close (fd); /* in case fdopen() failure */
+ }
+ else if (fstat (fd,&sbuf)) { /* get file size */
+ sprintf (LOCAL->buf,staterr,burp->name,strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ fclose (f);
+ }
+
+ /* only if sane */
+ else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) {
+ /* make sure each range starts with token */
+ for (set = &burp->set; set; set = set->next)
+ if (fseek (f,set->first,SEEK_SET) ||
+ (fread (LOCAL->buf,1,MSGTSZ,f) != MSGTSZ) ||
+ strncmp (LOCAL->buf,MSGTOK,MSGTSZ)) {
+ sprintf (LOCAL->buf,"Bad message token in mix message file at %lu",
+ set->first);
+ MM_LOG (LOCAL->buf,ERROR);
+ fclose (f);
+ return NIL; /* burp fails for this file */
+ }
+ /* burp out each old message */
+ for (set = &burp->set, wpos = 0; set; set = set->next) {
+ /* move down this range */
+ for (rpos = set->first, size = set->last - set->first;
+ size; size -= wsize) {
+ if (rpos != wpos) { /* data to skip at start? */
+ /* no, slide this buffer down */
+ wsize = min (size,LOCAL->buflen);
+ /* failure is not an option here */
+ while (fseek (f,rpos,SEEK_SET) ||
+ (fread (LOCAL->buf,1,wsize,f) != wsize)) {
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ /* nor here */
+ while (fseek (f,wpos,SEEK_SET)) {
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ /* and especially not here */
+ for (s = LOCAL->buf, wpending = wsize; wpending; wpending -= written)
+ if (!(written = fwrite (LOCAL->buf,1,wpending,f))) {
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ }
+ else wsize = size; /* nothing to skip, say we wrote it all */
+ rpos += wsize; wpos += wsize;
+ }
+ }
+
+ while (fflush (f)) { /* failure also not an option here... */
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ if (ftruncate (fd,wpos)) { /* flush cruft at end of file */
+ sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno));
+ MM_LOG (LOCAL->buf,WARN);
+ }
+ else *reclaimed += rpos - wpos;
+ ret = !fclose (f); /* close file */
+ /* slide down message positions in index */
+ for (i = 1,rpos = 0; i <= stream->nmsgs; ++i)
+ if ((elt = mail_elt (stream,i))->private.spare.data == burp->fileno) {
+ elt->private.special.offset = rpos;
+ rpos += elt->private.msg.header.offset + elt->rfc822_size;
+ }
+ /* debugging */
+ if (rpos != wpos) fatal ("burp size consistency check!");
+ }
+ return ret;
+}
+
+
+/* MIX burp sanity check to make sure not burping off end of file
+ * Accepts: burp set
+ * file size
+ * file name
+ * Returns: T if sane, NIL if insane
+ */
+
+long mix_burp_check (SEARCHSET *set,size_t size,char *file)
+{
+ do if (set->last > size) { /* sanity check */
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Unexpected short mix message file %.80s %lu < %lu",
+ file,size,set->last);
+ MM_LOG (tmp,ERROR);
+ return NIL; /* don't burp this file at all */
+ } while (set = set->next);
+ return LONGT;
+}
+
+/* MIX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long mix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ FDDATA d;
+ STRING st;
+ char tmp[2*MAILTMPLEN];
+ long ret = mix_isvalid (mailbox,LOCAL->buf);
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ MAILSTREAM *astream = NIL;
+ FILE *idxf = NIL;
+ FILE *msgf = NIL;
+ FILE *statf = NIL;
+ if (!ret) switch (errno) { /* make sure valid mailbox */
+ case NIL: /* no error in stat() */
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ break;
+ default: /* some stat() error */
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ break;
+ }
+ /* get sequence to copy */
+ else if (!(ret = ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))));
+ /* acquire stream to append */
+ else if (ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) &&
+ !astream->rdonly &&
+ (((MIXLOCAL *) astream->local)->expok = T) &&
+ (statf = mix_parse (astream,&idxf,LONGT,NIL))) ?
+ LONGT : NIL) {
+ int fd;
+ unsigned long i;
+ MESSAGECACHE *elt;
+ unsigned long newsize,hdrsize,size;
+ MIXLOCAL *local = (MIXLOCAL *) astream->local;
+ unsigned long seq = mix_modseq (local->metaseq);
+ /* make sure new modseq fits */
+ if (local->indexseq > seq) seq = local->indexseq + 1;
+ if (local->statusseq > seq) seq = local->statusseq + 1;
+ /* calculate size of per-message header */
+ sprintf (local->buf,MSRFMT,MSGTOK,0,0,0,0,0,0,0,'+',0,0,0);
+ hdrsize = strlen (local->buf);
+
+ MM_CRITICAL (stream); /* go critical */
+ astream->silent = T; /* no events here */
+ /* calculate size that will be added */
+ for (i = 1, newsize = 0; i <= stream->nmsgs; ++i)
+ if ((elt = mail_elt (stream,i))->sequence)
+ newsize += hdrsize + elt->rfc822_size;
+ /* open data file */
+ if (msgf = mix_data_open (astream,&fd,&size,newsize)) {
+ char *t;
+ unsigned long j,uid,uidv;
+ copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ for (i = 1,uid = uidv = 0; ret && (i <= stream->nmsgs); ++i)
+ if (((elt = mail_elt (stream,i))->sequence) && elt->rfc822_size) {
+ /* is message in current message file? */
+ if ((LOCAL->msgfd < 0) ||
+ (elt->private.spare.data != LOCAL->curmsg)) {
+ if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
+ if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,
+ stream->mailbox,
+ elt->private.spare.data),
+ O_RDONLY,NIL)) >= 0)
+ LOCAL->curmsg = elt->private.spare.data;
+ }
+ if (LOCAL->msgfd < 0) ret = NIL;
+ else { /* got file */
+ d.fd = LOCAL->msgfd;/* set up file descriptor */
+ /* start of message */
+ d.pos = elt->private.special.offset +
+ elt->private.msg.header.offset;
+ d.chunk = LOCAL->buf;
+ d.chunksize = CHUNKSIZE;
+ INIT (&st,fd_string,&d,elt->rfc822_size);
+ /* init flag string */
+ tmp[0] = tmp[1] = '\0';
+ if (j = elt->user_flags) do
+ if ((t = stream->user_flags[find_rightmost_bit (&j)]) && *t)
+ strcat (strcat (tmp," "),t);
+ while (j);
+ if (elt->seen) strcat (tmp," \\Seen");
+ if (elt->deleted) strcat (tmp," \\Deleted");
+ if (elt->flagged) strcat (tmp," \\Flagged");
+ if (elt->answered) strcat (tmp," \\Answered");
+ if (elt->draft) strcat (tmp," \\Draft");
+ tmp[0] = '('; /* wrap list */
+ strcat (tmp,")");
+ /* if append OK, add to source set */
+ if ((ret = mix_append_msg (astream,msgf,tmp,elt,&st,dest,
+ seq)) && source)
+ mail_append_set (source,mail_uid (stream,i));
+ }
+ }
+
+ /* finish write if success */
+ if (ret && (ret = !fflush (msgf))) {
+ fclose (msgf); /* all good, close the msg file now */
+ /* write new metadata, index, and status */
+ local->metaseq = local->indexseq = local->statusseq = seq;
+ if (ret = (mix_meta_update (astream) &&
+ mix_index_update (astream,idxf,LONGT))) {
+ /* success, delete if doing a move */
+ if (options & CP_MOVE)
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ elt->deleted = T;
+ if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq;
+ MM_FLAGS (stream,elt->msgno);
+ }
+ /* done with status file now */
+ mix_status_update (astream,statf,LONGT);
+ /* return sets if doing COPYUID */
+ if (cu) (*cu) (stream,mailbox,astream->uid_validity,source,dest);
+ source = dest = NIL; /* don't free these sets now */
+ }
+ }
+ else { /* error */
+ if (errno) { /* output error message if system call error */
+ sprintf (tmp,"Message copy failed: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ ftruncate (fd,size); /* revert file */
+ close (fd); /* make sure that fclose doesn't corrupt us */
+ fclose (msgf); /* free the stdio resources */
+ }
+ /* flush any sets remaining */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ else { /* message file open failed */
+ sprintf (tmp,"Error opening copy message file: %.80s",
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL;
+ }
+ MM_NOCRITICAL (stream);
+ }
+ else MM_LOG ("Can't open copy mailbox",ERROR);
+ if (statf) fclose (statf); /* close status if still open */
+ if (idxf) fclose (idxf); /* close index if still open */
+ /* finished with append stream */
+ if (astream) mail_close (astream);
+ return ret; /* return state */
+}
+
+/* MIX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ STRING *message;
+ char *flags,*date,tmp[MAILTMPLEN];
+ /* N.B.: can't use LOCAL->buf for tmp */
+ long ret = mix_isvalid (mailbox,tmp);
+ /* default stream to prototype */
+ if (!stream) stream = user_flags (&mixproto);
+ if (!ret) switch (errno) { /* if not valid mailbox */
+ case ENOENT: /* no such file? */
+ if (ret = compare_cstring (mailbox,"INBOX") ?
+ NIL : mix_create (NIL,"INBOX"))
+ break;
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ break;
+ default:
+ sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ break;
+ }
+
+ /* get first message */
+ if (ret && MM_APPEND (af) (stream,data,&flags,&date,&message)) {
+ MAILSTREAM *astream;
+ FILE *idxf = NIL;
+ FILE *msgf = NIL;
+ FILE *statf = NIL;
+ if (ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) &&
+ !astream->rdonly &&
+ (((MIXLOCAL *) astream->local)->expok = T) &&
+ (statf = mix_parse (astream,&idxf,LONGT,NIL))) ?
+ LONGT : NIL) {
+ int fd;
+ unsigned long size,hdrsize;
+ MESSAGECACHE elt;
+ MIXLOCAL *local = (MIXLOCAL *) astream->local;
+ unsigned long seq = mix_modseq (local->metaseq);
+ /* make sure new modseq fits */
+ if (local->indexseq > seq) seq = local->indexseq + 1;
+ if (local->statusseq > seq) seq = local->statusseq + 1;
+ /* calculate size of per-message header */
+ sprintf (local->buf,MSRFMT,MSGTOK,0,0,0,0,0,0,0,'+',0,0,0);
+ hdrsize = strlen (local->buf);
+ MM_CRITICAL (astream); /* go critical */
+ astream->silent = T; /* no events here */
+ /* open data file */
+ if (msgf = mix_data_open (astream,&fd,&size,hdrsize + SIZE (message))) {
+ appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ while (ret && message) {/* while good to go and have messages */
+ errno = NIL; /* in case one of these causes failure */
+ /* guard against zero-length */
+ if (!(ret = SIZE (message)))
+ MM_LOG ("Append of zero-length message",ERROR);
+ else if (date && !(ret = mail_parse_date (&elt,date))) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ }
+ else {
+ if (!date) { /* if date not specified, use now */
+ internal_date (tmp);
+ mail_parse_date (&elt,tmp);
+ }
+ ret = mix_append_msg (astream,msgf,flags,&elt,message,dst,seq) &&
+ MM_APPEND (af) (stream,data,&flags,&date,&message);
+ }
+ }
+
+ /* finish write if success */
+ if (ret && (ret = !fflush (msgf))) {
+ fclose (msgf); /* all good, close the msg file now */
+ /* write new metadata, index, and status */
+ local->metaseq = local->indexseq = local->statusseq = seq;
+ if ((ret = (mix_meta_update (astream) &&
+ mix_index_update (astream,idxf,LONGT) &&
+ mix_status_update (astream,statf,LONGT))) && au) {
+ (*au) (mailbox,astream->uid_validity,dst);
+ dst = NIL; /* don't free this set now */
+ }
+ }
+ else { /* failure */
+ if (errno) { /* output error message if system call error */
+ sprintf (tmp,"Message append failed: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ ftruncate (fd,size); /* revert all writes to file*/
+ close (fd); /* make sure that fclose doesn't corrupt us */
+ fclose (msgf); /* free the stdio resources */
+ }
+ /* flush any set remaining */
+ mail_free_searchset (&dst);
+ }
+ else { /* message file open failed */
+ sprintf (tmp,"Error opening append message file: %.80s",
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL;
+ }
+ MM_NOCRITICAL (astream); /* release critical */
+ }
+ else MM_LOG ("Can't open append mailbox",ERROR);
+ if (statf) fclose (statf); /* close status if still open */
+ if (idxf) fclose (idxf); /* close index if still open */
+ if (astream) mail_close (astream);
+ }
+ return ret;
+}
+
+/* MIX mail append single message
+ * Accepts: MAIL stream
+ * flags for new message if non-NIL
+ * elt with source date if non-NIL
+ * stringstruct of message text
+ * searchset to place UID
+ * modseq of message
+ * Returns: T if success, NIL if failure
+ */
+
+long mix_append_msg (MAILSTREAM *stream,FILE *f,char *flags,MESSAGECACHE *delt,
+ STRING *msg,SEARCHSET *set,unsigned long seq)
+{
+ MESSAGECACHE *elt;
+ int c,cs;
+ unsigned long i,j,k,uf,hoff;
+ long sf;
+ stream->kwd_create = NIL; /* don't copy unknown keywords */
+ sf = mail_parse_flags (stream,flags,&uf);
+ /* swell the cache */
+ mail_exists (stream,++stream->nmsgs);
+ /* assign new UID from metadata */
+ (elt = mail_elt (stream,stream->nmsgs))->private.uid = ++stream->uid_last;
+ elt->private.mod = seq; /* set requested modseq in status */
+ elt->rfc822_size = SIZE (msg);/* copy message size and date to index */
+ elt->year = delt->year; elt->month = delt->month; elt->day = delt->day;
+ elt->hours = delt->hours; elt->minutes = delt->minutes;
+ elt->seconds = delt->seconds; elt->zoccident = delt->zoccident;
+ elt->zhours = delt->zhours; elt->zminutes = delt->zminutes;
+ /*
+ * Do NOT set elt->valid here! mix_status_update() uses it to determine
+ * whether a message should be marked as old.
+ */
+ if (sf&fSEEN) elt->seen = T; /* copy flags to status */
+ if (sf&fDELETED) elt->deleted = T;
+ if (sf&fFLAGGED) elt->flagged = T;
+ if (sf&fANSWERED) elt->answered = T;
+ if (sf&fDRAFT) elt->draft = T;
+ elt->user_flags |= uf;
+ /* message is in new message file */
+ elt->private.spare.data = LOCAL->newmsg;
+
+ /* offset to message internal header */
+ elt->private.special.offset = ftell (f);
+ /* build header for message */
+ fprintf (f,MSRFMT,MSGTOK,elt->private.uid,
+ elt->year + BASEYEAR,elt->month,elt->day,
+ elt->hours,elt->minutes,elt->seconds,
+ elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes,
+ elt->rfc822_size);
+ /* offset to header from internal header */
+ elt->private.msg.header.offset = ftell (f) - elt->private.special.offset;
+ for (cs = 0; SIZE (msg); ) { /* copy message */
+ if (elt->private.msg.header.text.size) {
+ if (msg->cursize) /* blat entire chunk if have it */
+ for (j = msg->cursize; j; j -= k)
+ if (!(k = fwrite (msg->curpos,1,j,f))) return NIL;
+ SETPOS (msg,GETPOS (msg) + msg->cursize);
+ }
+ else { /* still searching for delimiter */
+ c = 0xff & SNX (msg); /* get source character */
+ if (putc (c,f) == EOF) return NIL;
+ switch (cs) { /* decide what to do based on state */
+ case 0: /* previous char ordinary */
+ if (c == '\015') cs = 1;/* advance if CR */
+ break;
+ case 1: /* previous CR, advance if LF */
+ cs = (c == '\012') ? 2 : 0;
+ break;
+ case 2: /* previous CRLF, advance if CR */
+ cs = (c == '\015') ? 3 : 0;
+ break;
+ case 3: /* previous CRLFCR, done if LF */
+ if (c == '\012') elt->private.msg.header.text.size =
+ elt->rfc822_size - SIZE (msg);
+ cs = 0; /* reset mechanism */
+ break;
+ }
+ }
+ }
+ /* if no delimiter, header is entire msg */
+ if (!elt->private.msg.header.text.size)
+ elt->private.msg.header.text.size = elt->rfc822_size;
+ /* add this message to set */
+ mail_append_set (set,elt->private.uid);
+ return LONGT; /* success */
+}
+
+/* MIX mail read metadata, index, and status
+ * Accepts: MAIL stream
+ * returned index file
+ * index file flags (non-NIL if want to add/remove messages)
+ * status file flags (non-NIL if want to update elt->valid and old)
+ * Returns: open status file, or NIL if failure
+ *
+ * Note that this routine can return an open index file even if it fails!
+ */
+
+static char *shortmsg =
+ "message %lu (UID=%.08lx) truncated by %lu byte(s) (%lu < %lu)";
+
+FILE *mix_parse (MAILSTREAM *stream,FILE **idxf,long iflags,long sflags)
+{
+ int fd;
+ unsigned long i;
+ char *s,*t;
+ struct stat sbuf;
+ FILE *statf = NIL;
+ short metarepairneeded = 0;
+ short indexrepairneeded = 0;
+ short silent = stream->silent;
+ *idxf = NIL; /* in case error */
+ /* readonly means no updates */
+ if (stream->rdonly) iflags = sflags = NIL;
+ /* open index file */
+ if ((fd = open (LOCAL->index,iflags ? O_RDWR : O_RDONLY,NIL)) < 0)
+ MM_LOG ("Error opening mix index file",ERROR);
+ /* acquire exclusive access and FILE */
+ else if (!flock (fd,iflags ? LOCK_EX : LOCK_SH) &&
+ !(*idxf = fdopen (fd,iflags ? "r+b" : "rb"))) {
+ MM_LOG ("Error obtaining stream on mix index file",ERROR);
+ flock (fd,LOCK_UN); /* relinquish lock */
+ close (fd);
+ }
+
+ /* slurp metadata */
+ else if (s = mix_meta_slurp (stream,&i)) {
+ unsigned long j = 0; /* non-zero if UIDVALIDITY/UIDLAST changed */
+ if (i != LOCAL->metaseq) { /* metadata changed? */
+ char *t,*k;
+ LOCAL->metaseq = i; /* note new metadata sequence */
+ while (s && *s) { /* parse entire metadata file */
+ /* locate end of line */
+ if (s = strstr (t = s,"\015\012")) {
+ *s = '\0'; /* tie off line */
+ s += 2; /* skip past CRLF */
+ switch (*t++) { /* parse line */
+ case 'V': /* UIDVALIDITY */
+ if (!isxdigit (*t) || !(i = strtoul (t,&t,16))) {
+ MM_LOG ("Error in mix metadata file UIDVALIDITY record",ERROR);
+ return NIL; /* give up */
+ }
+ if (i != stream->uid_validity) j = stream->uid_validity = i;
+ break;
+ case 'L': /* new UIDLAST */
+ if (!isxdigit (*t)) {
+ MM_LOG ("Error in mix metadata file UIDLAST record",ERROR);
+ return NIL; /* give up */
+ }
+ if ((i = strtoul (t,&t,16)) != stream->uid_last)
+ j = stream->uid_last = i;
+ break;
+ case 'N': /* new message file */
+ if (!isxdigit (*t)) {
+ MM_LOG ("Error in mix metadata file new msg record",ERROR);
+ return NIL; /* give up */
+ }
+ if ((i = strtoul (t,&t,16)) != stream->uid_last)
+ LOCAL->newmsg = i;
+ break;
+ case 'K': /* new keyword list */
+ for (i = 0; t && *t && (i < NUSERFLAGS); ++i) {
+ if (t = strchr (k = t,' ')) *t++ = '\0';
+ /* make sure keyword non-empty */
+ if (*k && (strlen (k) <= MAXUSERFLAG)) {
+ /* in case value changes (shouldn't happen) */
+ if (stream->user_flags[i] && strcmp (stream->user_flags[i],k)){
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"flag rename old=%.80s new=%.80s",
+ stream->user_flags[i],k);
+ MM_LOG (tmp,WARN);
+ fs_give ((void **) &stream->user_flags[i]);
+ }
+ if (!stream->user_flags[i]) stream->user_flags[i] = cpystr (k);
+ }
+ else break; /* empty keyword */
+ }
+ if ((i < NUSERFLAGS) && stream->user_flags[i]) {
+ MM_LOG ("Error in mix metadata file keyword record",ERROR);
+ return NIL; /* give up */
+ }
+ else if (i == NUSERFLAGS) stream->kwd_create = NIL;
+ break;
+ }
+ }
+ if (t && *t) { /* junk in line */
+ MM_LOG ("Error in mix metadata record",ERROR);
+ return NIL; /* give up */
+ }
+ }
+ }
+
+ /* get sequence */
+ if (!(i = mix_read_sequence (*idxf)) || (i < LOCAL->indexseq)) {
+ MM_LOG ("Error in mix index file sequence record",ERROR);
+ return NIL; /* give up */
+ }
+ /* sequence changed from last time? */
+ else if (j || (i > LOCAL->indexseq)) {
+ unsigned long uid,nmsgs,curfile,curfilesize,curpos;
+ char *t,*msg,tmp[MAILTMPLEN];
+ /* start with no messages */
+ curfile = curfilesize = curpos = nmsgs = 0;
+ /* update sequence iff expunging OK */
+ if (LOCAL->expok) LOCAL->indexseq = i;
+ /* get first elt */
+ while ((s = mix_read_record (*idxf,LOCAL->buf,LOCAL->buflen,"index")) &&
+ *s)
+ switch (*s) {
+ case ':': /* message record */
+ if (!(isxdigit (*++s) && (uid = strtoul (s,&t,16)))) msg = "UID";
+ else if (!((*t++ == ':') && isdigit (*t) && isdigit (t[1]) &&
+ isdigit (t[2]) && isdigit (t[3]) && isdigit (t[4]) &&
+ isdigit (t[5]) && isdigit (t[6]) && isdigit (t[7]) &&
+ isdigit (t[8]) && isdigit (t[9]) && isdigit (t[10]) &&
+ isdigit (t[11]) && isdigit (t[12]) && isdigit (t[13]) &&
+ ((t[14] == '+') || (t[14] == '-')) &&
+ isdigit (t[15]) && isdigit (t[16]) && isdigit (t[17]) &&
+ isdigit (t[18]))) msg = "internaldate";
+ else if ((*(s = t+19) != ':') || !isxdigit (*++s)) msg = "size";
+ else {
+ unsigned int y = (((*t - '0') * 1000) + ((t[1] - '0') * 100) +
+ ((t[2] - '0') * 10) + t[3] - '0') - BASEYEAR;
+ unsigned int m = ((t[4] - '0') * 10) + t[5] - '0';
+ unsigned int d = ((t[6] - '0') * 10) + t[7] - '0';
+ unsigned int hh = ((t[8] - '0') * 10) + t[9] - '0';
+ unsigned int mm = ((t[10] - '0') * 10) + t[11] - '0';
+ unsigned int ss = ((t[12] - '0') * 10) + t[13] - '0';
+ unsigned int z = (t[14] == '-') ? 1 : 0;
+ unsigned int zh = ((t[15] - '0') * 10) + t[16] - '0';
+ unsigned int zm = ((t[17] - '0') * 10) + t[18] - '0';
+ unsigned long size = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ unsigned long file = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ unsigned long pos = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ unsigned long hpos = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ unsigned long hsiz = strtoul (s,&s,16);
+ if (uid > stream->uid_last) {
+ sprintf (tmp,"mix index invalid UID (%08lx < %08lx)",
+ uid,stream->uid_last);
+ if (stream->rdonly) {
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ strcat (tmp,", repaired");
+ MM_LOG (tmp,WARN);
+ stream->uid_last = uid;
+ metarepairneeded = T;
+ }
+
+ /* ignore expansion values */
+ if (*s++ == ':') {
+ MESSAGECACHE *elt;
+ ++nmsgs; /* this is another mesage */
+ /* within current known range of messages? */
+ while (nmsgs <= stream->nmsgs) {
+ /* yes, get corresponding elt */
+ elt = mail_elt (stream,nmsgs);
+ /* existing message with matching data? */
+ if (uid == elt->private.uid) {
+ /* beware of Dracula's resurrection */
+ if (elt->private.ghost) {
+ sprintf (tmp,"mix index data unexpunged UID: %lx",
+ uid);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* also of static data changing */
+ if ((size != elt->rfc822_size) ||
+ (file != elt->private.spare.data) ||
+ (pos != elt->private.special.offset) ||
+ (hpos != elt->private.msg.header.offset) ||
+ (hsiz != elt->private.msg.header.text.size) ||
+ (y != elt->year) || (m != elt->month) ||
+ (d != elt->day) || (hh != elt->hours) ||
+ (mm != elt->minutes) || (ss != elt->seconds) ||
+ (z != elt->zoccident) || (zh != elt->zhours) ||
+ (zm != elt->zminutes)) {
+ sprintf (tmp,"mix index data mismatch: %lx",uid);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ break;
+ }
+ /* existing msg with lower UID is expunged */
+ else if (uid > elt->private.uid) {
+ if (LOCAL->expok) mail_expunged (stream,nmsgs);
+ else {/* message expunged, but not yet for us */
+ ++nmsgs;
+ elt->private.ghost = T;
+ }
+ }
+ else { /* unexpected message record */
+ sprintf (tmp,"mix index UID mismatch (%lx < %lx)",
+ uid,elt->private.uid);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ }
+
+ /* time to create a new message? */
+ if (nmsgs > stream->nmsgs) {
+ /* defer announcing until later */
+ stream->silent = T;
+ mail_exists (stream,nmsgs);
+ stream->silent = silent;
+ (elt = mail_elt (stream,nmsgs))->recent = T;
+ elt->private.uid = uid; elt->rfc822_size = size;
+ elt->private.spare.data = file;
+ elt->private.special.offset = pos;
+ elt->private.msg.header.offset = hpos;
+ elt->private.msg.header.text.size = hsiz;
+ elt->year = y; elt->month = m; elt->day = d;
+ elt->hours = hh; elt->minutes = mm;
+ elt->seconds = ss; elt->zoccident = z;
+ elt->zhours = zh; elt->zminutes = zm;
+ /* message in same file? */
+ if (curfile == file) {
+ if (pos < curpos) {
+ MESSAGECACHE *plt = mail_elt (stream,elt->msgno-1);
+ /* uh-oh, calculate delta? */
+ i = curpos - pos;
+ sprintf (tmp,shortmsg,plt->msgno,plt->private.uid,
+ i,pos,curpos);
+ /* possible to fix? */
+ if (!stream->rdonly && LOCAL->expok &&
+ (i < plt->rfc822_size)) {
+ plt->rfc822_size -= i;
+ if (plt->rfc822_size <
+ plt->private.msg.header.text.size)
+ plt->private.msg.header.text.size =
+ plt->rfc822_size;
+ strcat (tmp,", repaired");
+ indexrepairneeded = T;
+ }
+ MM_LOG (tmp,WARN);
+ }
+ }
+ else { /* new file, restart */
+ if (stat (mix_file_data (LOCAL->buf,stream->mailbox,
+ curfile = file),&sbuf)) {
+ sprintf (tmp,"Missing mix data file: %.500s",
+ LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ curfile = file;
+ curfilesize = sbuf.st_size;
+ }
+
+ /* position of message in file */
+ curpos = pos + elt->private.msg.header.offset +
+ elt->rfc822_size;
+ /* short file? */
+ if (curfilesize < curpos) {
+ /* uh-oh, calculate delta? */
+ i = curpos - curfilesize;
+ sprintf (tmp,shortmsg,elt->msgno,elt->private.uid,
+ i,curfilesize,curpos);
+ /* possible to fix? */
+ if (!stream->rdonly && LOCAL->expok &&
+ (i < elt->rfc822_size)) {
+ elt->rfc822_size -= i;
+ if (elt->rfc822_size <
+ elt->private.msg.header.text.size)
+ elt->private.msg.header.text.size =
+ elt->rfc822_size;
+ strcat (tmp,", repaired");
+ indexrepairneeded = T;
+ }
+ MM_LOG (tmp,WARN);
+ }
+ }
+ break;
+ }
+ else msg = "expansion";
+ }
+ else msg = "header size";
+ }
+ else msg = "header position";
+ }
+ else msg = "message position";
+ }
+ else msg = "file#";
+ }
+ sprintf (tmp,"Error in %s in mix index file: %.500s",msg,s);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Unknown record in mix index file: %.500s",s);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ if (!s) return NIL; /* barfage from mix_read_record() */
+ /* expunge trailing messages not in index */
+ if (LOCAL->expok) while (nmsgs < stream->nmsgs)
+ mail_expunged (stream,stream->nmsgs);
+ }
+
+ /* repair metadata and index if needed */
+ if ((metarepairneeded ? mix_meta_update (stream) : T) &&
+ (indexrepairneeded ? mix_index_update (stream,*idxf,NIL) : T)) {
+ MESSAGECACHE *elt;
+ int fd;
+ unsigned long uid,uf,sf,mod;
+ char *s;
+ int updatep = NIL;
+ /* open status file */
+ if ((fd = open (LOCAL->status,
+ stream->rdonly ? O_RDONLY : O_RDWR,NIL)) < 0)
+ MM_LOG ("Error opening mix status file",ERROR);
+ /* acquire exclusive access and FILE */
+ else if (!flock (fd,stream->rdonly ? LOCK_SH : LOCK_EX) &&
+ !(statf = fdopen (fd,stream->rdonly ? "rb" : "r+b"))) {
+ MM_LOG ("Error obtaining stream on mix status file",ERROR);
+ flock (fd,LOCK_UN); /* relinquish lock */
+ close (fd);
+ }
+ /* get sequence */
+ else if (!(i = mix_read_sequence (statf)) ||
+ ((i < LOCAL->statusseq) && stream->nmsgs && (i != 1))) {
+ sprintf (LOCAL->buf,
+ "Error in mix status sequence record, i=%lx, seq=%lx",
+ i,LOCAL->statusseq);
+ MM_LOG (LOCAL->buf,ERROR);
+ }
+ /* sequence changed from last time? */
+ else if (i != LOCAL->statusseq) {
+ /* update sequence, get first elt */
+ if (i > LOCAL->statusseq) LOCAL->statusseq = i;
+ if (stream->nmsgs) {
+ elt = mail_elt (stream,i = 1);
+
+ /* read message records */
+ while ((t = s = mix_read_record (statf,LOCAL->buf,LOCAL->buflen,
+ "status")) && *s && (*s++ == ':') &&
+ isxdigit (*s)) {
+ uid = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ uf = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ sf = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ mod = strtoul (s,&s,16);
+ /* ignore expansion values */
+ if (*s++ == ':') {
+ /* need to move ahead to next elt? */
+ while ((uid > elt->private.uid) && (i < stream->nmsgs))
+ elt = mail_elt (stream,++i);
+ /* update elt if altered */
+ if ((uid == elt->private.uid) &&
+ (!elt->valid || (mod != elt->private.mod))) {
+ elt->user_flags = uf;
+ elt->private.mod = mod;
+ elt->seen = (sf & fSEEN) ? T : NIL;
+ elt->deleted = (sf & fDELETED) ? T : NIL;
+ elt->flagged = (sf & fFLAGGED) ? T : NIL;
+ elt->answered = (sf & fANSWERED) ? T : NIL;
+ elt->draft = (sf & fDRAFT) ? T : NIL;
+ /* announce if altered existing message */
+ if (elt->valid) MM_FLAGS (stream,elt->msgno);
+ /* first time, is old message? */
+ else if (sf & fOLD) {
+ /* yes, clear recent and set valid */
+ elt->recent = NIL;
+ elt->valid = T;
+ }
+ /* recent, allowed to update its status? */
+ else if (sflags) {
+ /* yes, set valid and check in status */
+ elt->valid = T;
+ elt->private.mod = mix_modseq (elt->private.mod);
+ updatep = T;
+ }
+ /* leave valid unset and recent if sflags not set */
+ }
+ continue; /* everything looks good */
+ }
+ }
+ }
+ }
+ break; /* error somewhere */
+ }
+
+ if (t && *t) { /* non-null means bogus record */
+ char msg[MAILTMPLEN];
+ sprintf (msg,"Error in mix status file message record%s: %.80s",
+ stream->rdonly ? "" : ", fixing",t);
+ MM_LOG (msg,WARN);
+ /* update it if not readonly */
+ if (!stream->rdonly) updatep = T;
+ }
+ if (updatep) { /* need to update? */
+ LOCAL->statusseq = mix_modseq (LOCAL->statusseq);
+ mix_status_update (stream,statf,LONGT);
+ }
+ }
+ }
+ }
+ }
+ if (statf) { /* still happy? */
+ unsigned long j;
+ stream->silent = silent; /* now notify upper level */
+ mail_exists (stream,stream->nmsgs);
+ for (i = 1, j = 0; i <= stream->nmsgs; ++i)
+ if (mail_elt (stream,i)->recent) ++j;
+ mail_recent (stream,j);
+ }
+ return statf;
+}
+
+/* MIX metadata file routines */
+
+/* MIX read metadata
+ * Accepts: MAIL stream
+ * return pointer for modseq
+ * Returns: pointer to metadata after modseq or NIL if failure
+ */
+
+char *mix_meta_slurp (MAILSTREAM *stream,unsigned long *seq)
+{
+ struct stat sbuf;
+ char *s;
+ char *ret = NIL;
+ if (fstat (LOCAL->mfd,&sbuf))
+ MM_LOG ("Error obtaining size of mix metatdata file",ERROR);
+ if (sbuf.st_size > LOCAL->buflen) {
+ /* should be just a few dozen bytes */
+ if (sbuf.st_size > METAMAX) fatal ("absurd mix metadata file size");
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = sbuf.st_size) + 1);
+ }
+ /* read current metadata file */
+ LOCAL->buf[sbuf.st_size] = '\0';
+ if (lseek (LOCAL->mfd,0,L_SET) ||
+ (read (LOCAL->mfd,s = LOCAL->buf,sbuf.st_size) != sbuf.st_size))
+ MM_LOG ("Error reading mix metadata file",ERROR);
+ else if ((*s != 'S') || !isxdigit (s[1]) ||
+ ((*seq = strtoul (s+1,&s,16)) < LOCAL->metaseq) ||
+ (*s++ != '\015') || (*s++ != '\012'))
+ MM_LOG ("Error in mix metadata file sequence record",ERROR);
+ else ret = s;
+ return ret;
+}
+
+/* MIX update metadata
+ * Accepts: MAIL stream
+ * Returns: T on success, NIL if error
+ *
+ * Index MUST be locked!!
+ */
+
+long mix_meta_update (MAILSTREAM *stream)
+{
+ long ret;
+ /* do nothing if stream readonly */
+ if (stream->rdonly) ret = LONGT;
+ else {
+ unsigned char c,*s,*ss,*t;
+ unsigned long i;
+ /* The worst-case metadata is limited to:
+ * 4 * (1 + 8 + 2) + (NUSERFLAGS * (MAXUSERFLAG + 1))
+ * which comes out to 1994 octets. This is much smaller than the normal
+ * CHUNKSIZE definition of 64K, and CHUNKSIZE is the smallest size of
+ * LOCAL->buf.
+ *
+ * If more stuff gets added to the metadata, or if you change the value
+ * of NUSERFLAGS, MAXUSERFLAG or CHUNKSIZE, be sure to recalculate the
+ * above assertation.
+ */
+ sprintf (LOCAL->buf,SEQFMT,LOCAL->metaseq = mix_modseq (LOCAL->metaseq));
+ sprintf (LOCAL->buf + strlen (LOCAL->buf),MTAFMT,
+ stream->uid_validity,stream->uid_last,LOCAL->newmsg);
+ for (i = 0, c = 'K', s = ss = LOCAL->buf + strlen (LOCAL->buf);
+ (i < NUSERFLAGS) && (t = stream->user_flags[i]); ++i) {
+ if (!*t) fatal ("impossible empty keyword");
+ *s++ = c; /* write delimiter */
+ while (*t) *s++ = *t++; /* write keyword */
+ c = ' '; /* delimiter is now space */
+ }
+ if (s != ss) { /* tie off keywords line */
+ *s++ = '\015'; *s++ = '\012';
+ }
+ /* calculate length of metadata */
+ if ((i = s - LOCAL->buf) > LOCAL->buflen)
+ fatal ("impossible buffer overflow");
+ lseek (LOCAL->mfd,0,L_SET); /* rewind file */
+ /* write new metadata */
+ ret = (write (LOCAL->mfd,LOCAL->buf,i) == i) ? LONGT : NIL;
+ ftruncate (LOCAL->mfd,i); /* and tie off at that point */
+ }
+ return ret;
+}
+
+/* MIX index file routines */
+
+
+/* MIX update index
+ * Accepts: MAIL stream
+ * open FILE
+ * expansion check flag
+ * Returns: T on success, NIL if error
+ */
+
+long mix_index_update (MAILSTREAM *stream,FILE *idxf,long flag)
+{
+ unsigned long i;
+ long ret = LONGT;
+ if (!stream->rdonly) { /* do nothing if stream readonly */
+ if (flag) { /* need to do expansion check? */
+ char tmp[MAILTMPLEN];
+ size_t size;
+ struct stat sbuf;
+ /* calculate file size we need */
+ for (i = 1, size = 0; i <= stream->nmsgs; ++i)
+ if (!mail_elt (stream,i)->private.ghost) ++size;
+ if (size) { /* Winston Smith's first dairy entry */
+ sprintf (tmp,IXRFMT,0,14,4,4,13,0,0,'+',0,0,0,0,0,0,0);
+ size *= strlen (tmp);
+ }
+ /* calculate file size we need */
+ sprintf (tmp,SEQFMT,LOCAL->indexseq);
+ size += strlen (tmp);
+ /* get current file size */
+ if (fstat (fileno (idxf),&sbuf)) {
+ MM_LOG ("Error getting size of mix index file",ERROR);
+ ret = NIL;
+ }
+ /* need to write additional space? */
+ else if (sbuf.st_size < size) {
+ void *buf = fs_get (size -= sbuf.st_size);
+ memset (buf,0,size);
+ if (fseek (idxf,0,SEEK_END) || (fwrite (buf,1,size,idxf) != size) ||
+ fflush (idxf)) {
+ fseek (idxf,sbuf.st_size,SEEK_SET);
+ ftruncate (fileno (idxf),sbuf.st_size);
+ MM_LOG ("Error extending mix index file",ERROR);
+ ret = NIL;
+ }
+ fs_give ((void **) &buf);
+ }
+ }
+
+ if (ret) { /* if still good to go */
+ rewind (idxf); /* let's start at the very beginning */
+ /* write modseq first */
+ fprintf (idxf,SEQFMT,LOCAL->indexseq);
+ /* then write all messages */
+ for (i = 1; ret && (i <= stream->nmsgs); i++) {
+ MESSAGECACHE *elt = mail_elt (stream,i);
+ if (!elt->private.ghost)/* only write living messages */
+ fprintf (idxf,IXRFMT,elt->private.uid,
+ elt->year + BASEYEAR,elt->month,elt->day,
+ elt->hours,elt->minutes,elt->seconds,
+ elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes,
+ elt->rfc822_size,elt->private.spare.data,
+ elt->private.special.offset,
+ elt->private.msg.header.offset,
+ elt->private.msg.header.text.size);
+ if (ferror (idxf)) {
+ MM_LOG ("Error updating mix index file",ERROR);
+ ret = NIL;
+ }
+ }
+ if (fflush (idxf)) {
+ MM_LOG ("Error flushing mix index file",ERROR);
+ ret = NIL;
+ }
+ if (ret) ftruncate (fileno (idxf),ftell (idxf));
+ }
+ }
+ return ret;
+}
+
+/* MIX status file routines */
+
+
+/* MIX update status
+ * Accepts: MAIL stream
+ * pointer to open FILE
+ * expansion check flag
+ * Returns: T on success, NIL if error
+ */
+
+long mix_status_update (MAILSTREAM *stream,FILE *statf,long flag)
+{
+ unsigned long i;
+ char tmp[MAILTMPLEN];
+ long ret = LONGT;
+ if (!stream->rdonly) { /* do nothing if stream readonly */
+ if (flag) { /* need to do expansion check? */
+ char tmp[MAILTMPLEN];
+ size_t size;
+ struct stat sbuf;
+ /* calculate file size we need */
+ for (i = 1, size = 0; i <= stream->nmsgs; ++i)
+ if (!mail_elt (stream,i)->private.ghost) ++size;
+ if (size) { /* number of living messages */
+ sprintf (tmp,STRFMT,0,0,0,0);
+ size *= strlen (tmp);
+ }
+ sprintf (tmp,SEQFMT,LOCAL->statusseq);
+ size += strlen (tmp);
+ /* get current file size */
+ if (fstat (fileno (statf),&sbuf)) {
+ MM_LOG ("Error getting size of mix status file",ERROR);
+ ret = NIL;
+ }
+ /* need to write additional space? */
+ else if (sbuf.st_size < size) {
+ void *buf = fs_get (size -= sbuf.st_size);
+ memset (buf,0,size);
+ if (fseek (statf,0,SEEK_END) || (fwrite (buf,1,size,statf) != size) ||
+ fflush (statf)) {
+ fseek (statf,sbuf.st_size,SEEK_SET);
+ ftruncate (fileno (statf),sbuf.st_size);
+ MM_LOG ("Error extending mix status file",ERROR);
+ ret = NIL;
+ }
+ fs_give ((void **) &buf);
+ }
+ }
+
+ if (ret) { /* if still good to go */
+ rewind (statf); /* let's start at the very beginning */
+ /* write sequence */
+ fprintf (statf,SEQFMT,LOCAL->statusseq);
+ /* write message status records */
+ for (i = 1; ret && (i <= stream->nmsgs); ++i) {
+ MESSAGECACHE *elt = mail_elt (stream,i);
+ /* make sure all messages have a modseq */
+ if (!elt->private.mod) elt->private.mod = LOCAL->statusseq;
+ if (!elt->private.ghost)/* only write living messages */
+ fprintf (statf,STRFMT,elt->private.uid,elt->user_flags,
+ (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft) + (elt->valid ? fOLD : NIL),
+ elt->private.mod);
+ if (ferror (statf)) {
+ sprintf (tmp,"Error updating mix status file: %.80s",
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL;
+ }
+ }
+ if (ret && fflush (statf)) {
+ MM_LOG ("Error flushing mix status file",ERROR);
+ ret = NIL;
+ }
+ if (ret) ftruncate (fileno (statf),ftell (statf));
+ }
+ }
+ return ret;
+}
+
+/* MIX data file routines */
+
+
+/* MIX open data file
+ * Accepts: MAIL stream
+ * pointer to returned fd if success
+ * pointer to returned size if success
+ * size of new data to be added
+ * Returns: open FILE, or NIL if failure
+ *
+ * The curend test assumes that the last message of the mailbox is the furthest
+ * point that the current data file extends, and thus that is all that needs to
+ * be tested for short file prevention.
+ */
+
+FILE *mix_data_open (MAILSTREAM *stream,int *fd,long *size,
+ unsigned long newsize)
+{
+ FILE *msgf = NIL;
+ struct stat sbuf;
+ MESSAGECACHE *elt = stream->nmsgs ? mail_elt (stream,stream->nmsgs) : NIL;
+ unsigned long curend = (elt && (elt->private.spare.data == LOCAL->newmsg)) ?
+ elt->private.special.offset + elt->private.msg.header.offset +
+ elt->rfc822_size : 0;
+ /* allow create if curend 0 */
+ if ((*fd = open (mix_file_data (LOCAL->buf,stream->mailbox,LOCAL->newmsg),
+ O_RDWR | (curend ? NIL : O_CREAT),NIL)) >= 0) {
+ fstat (*fd,&sbuf); /* get current file size */
+ /* can we use this file? */
+ if ((curend <= sbuf.st_size) &&
+ (!sbuf.st_size || ((sbuf.st_size + newsize) <= MIXDATAROLL)))
+ *size = sbuf.st_size; /* yes, return current size */
+ else { /* short file or becoming too long */
+ if (curend > sbuf.st_size) {
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"short mix message file %.08lx (%ld > %ld), rolling",
+ LOCAL->newmsg,curend,sbuf.st_size);
+ MM_LOG (tmp,WARN); /* shouldn't happen */
+ }
+ close (*fd); /* roll to a new file */
+ while ((*fd = open (mix_file_data
+ (LOCAL->buf,stream->mailbox,
+ LOCAL->newmsg = mix_modseq (LOCAL->newmsg)),
+ O_RDWR | O_CREAT | O_EXCL,sbuf.st_mode)) < 0);
+ *size = 0; /* brand new file */
+ fchmod (*fd,sbuf.st_mode);/* with same mode as previous file */
+ }
+ }
+ if (*fd >= 0) { /* have a data file? */
+ /* yes, get stdio and set position */
+ if (msgf = fdopen (*fd,"r+b")) fseek (msgf,*size,SEEK_SET);
+ else close (*fd); /* fdopen() failed? */
+ }
+ return msgf; /* return results */
+}
+
+/* MIX open sortcache
+ * Accepts: MAIL stream
+ * Returns: open FILE, or NIL if failure or could only get readonly sortcache
+ */
+
+FILE *mix_sortcache_open (MAILSTREAM *stream)
+{
+ int fd,refwd;
+ unsigned long i,uid,sentdate,fromlen,tolen,cclen,subjlen,msgidlen,reflen;
+ char *s,*t,*msg,tmp[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ SORTCACHE *sc;
+ STRINGLIST *sl;
+ struct stat sbuf;
+ int rdonly = NIL;
+ FILE *srtcf = NIL;
+ mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
+ fstat (LOCAL->mfd,&sbuf);
+ if (!stream->nmsgs); /* do nothing if mailbox empty */
+ /* open sortcache file */
+ else if (((fd = open (LOCAL->sortcache,O_RDWR|O_CREAT,sbuf.st_mode)) < 0) &&
+ !(rdonly = ((fd = open (LOCAL->sortcache,O_RDONLY,NIL)) >= 0)))
+ MM_LOG ("Error opening mix sortcache file",WARN);
+ /* acquire lock and FILE */
+ else if (!flock (fd,rdonly ? LOCK_SH : LOCK_EX) &&
+ !(srtcf = fdopen (fd,rdonly ? "rb" : "r+b"))) {
+ MM_LOG ("Error obtaining stream on mix sortcache file",WARN);
+ flock (fd,LOCK_UN); /* relinquish lock */
+ close (fd);
+ }
+ else if (!(i = mix_read_sequence (srtcf)) || (i < LOCAL->sortcacheseq))
+ MM_LOG ("Error in mix sortcache file sequence record",WARN);
+ /* sequence changed from last time? */
+ else if (i > LOCAL->sortcacheseq) {
+ LOCAL->sortcacheseq = i; /* update sequence */
+ while ((s = t = mix_read_record (srtcf,LOCAL->buf,LOCAL->buflen,
+ "sortcache")) && *s &&
+ (msg = "uid") && (*s++ == ':') && isxdigit (*s)) {
+ uid = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ sentdate = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ fromlen = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ tolen = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ cclen = strtoul (s,&s,16);
+ if ((*s++ == ':') && ((*s == 'R') || (*s == ' ')) &&
+ isxdigit (s[1])) {
+ refwd = (*s++ == 'R') ? T : NIL;
+ subjlen = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ msgidlen = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ reflen = strtoul (s,&s,16);
+ /* ignore expansion values */
+ if (*s++ == ':') {
+
+ if (i = mail_msgno (stream,uid)) {
+ sc = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE);
+ sc->size = (elt = mail_elt (stream,i))->rfc822_size;
+ sc->date = sentdate;
+ sc->arrival = elt->day ? mail_longdate (elt) : 1;
+ if (refwd) sc->refwd = T;
+ if (fromlen) {
+ if (sc->from) fseek (srtcf,fromlen + 2,SEEK_CUR);
+ else if ((getc (srtcf) != 'F') ||
+ (fread (sc->from = (char *) fs_get(fromlen),
+ 1,fromlen-1,srtcf) != (fromlen-1))||
+ (sc->from[fromlen-1] = '\0') ||
+ (getc (srtcf) != '\015') ||
+ (getc (srtcf) != '\012')) {
+ msg = "from data";
+ break;
+ }
+ }
+ if (tolen) {
+ if (sc->to) fseek (srtcf,tolen + 2,SEEK_CUR);
+ else if ((getc (srtcf) != 'T') ||
+ (fread (sc->to = (char *) fs_get (tolen),
+ 1,tolen-1,srtcf) != (tolen - 1)) ||
+ (sc->to[tolen-1] = '\0') ||
+ (getc (srtcf) != '\015') ||
+ (getc (srtcf) != '\012')) {
+ msg = "to data";
+ break;
+ }
+ }
+ if (cclen) {
+ if (sc->cc) fseek (srtcf,cclen + 2,SEEK_CUR);
+ else if ((getc (srtcf) != 'C') ||
+ (fread (sc->cc = (char *) fs_get (cclen),
+ 1,cclen-1,srtcf) != (cclen - 1)) ||
+ (sc->cc[cclen-1] = '\0') ||
+ (getc (srtcf) != '\015') ||
+ (getc (srtcf) != '\012')) {
+ msg = "cc data";
+ break;
+ }
+ }
+ if (subjlen) {
+ if (sc->subject) fseek (srtcf,subjlen + 2,SEEK_CUR);
+ else if ((getc (srtcf) != 'S') ||
+ (fread (sc->subject =
+ (char *) fs_get (subjlen),1,
+ subjlen-1,srtcf) != (subjlen-1))||
+ (sc->subject[subjlen-1] = '\0') ||
+ (getc (srtcf) != '\015') ||
+ (getc (srtcf) != '\012')) {
+ msg = "subject data";
+ break;
+ }
+ }
+
+ if (msgidlen) {
+ if (sc->message_id)
+ fseek (srtcf,msgidlen + 2,SEEK_CUR);
+ else if ((getc (srtcf) != 'M') ||
+ (fread (sc->message_id =
+ (char *) fs_get (msgidlen),1,
+ msgidlen-1,srtcf) != (msgidlen-1))||
+ (sc->message_id[msgidlen-1] = '\0') ||
+ (getc (srtcf) != '\015') ||
+ (getc (srtcf) != '\012')) {
+ msg = "message-id data";
+ break;
+ }
+ }
+ if (reflen) {
+ if (sc->references) fseek(srtcf,reflen + 2,SEEK_CUR);
+ /* make sure it fits */
+ else {
+ if (reflen >= LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *)
+ fs_get ((LOCAL->buflen = reflen) + 1);
+ }
+ if ((getc (srtcf) != 'R') ||
+ (fread (LOCAL->buf,1,reflen-1,srtcf) !=
+ (reflen - 1)) ||
+ (LOCAL->buf[reflen-1] = '\0') ||
+ (getc (srtcf) != '\015') ||
+ (getc (srtcf) != '\012')) {
+ msg = "references data";
+ break;
+ }
+ for (s = LOCAL->buf,sl = NIL,
+ sc->references = mail_newstringlist ();
+ s && *s; s += i + 1) {
+ if ((i = strtoul (s,&s,16)) && (*s++ == ':') &&
+ (s[i] == ':')) {
+ if (sl) sl = sl->next = mail_newstringlist();
+ else sl = sc->references;
+ s[i] = '\0';
+ sl->text.data = cpystr (s);
+ sl->text.size = i;
+ }
+ else s = NIL;
+ }
+ if (!s || *s ||
+ (s != ((char *) LOCAL->buf + reflen - 1))) {
+ msg = "references length consistency check";
+ break;
+ }
+ }
+ }
+ }
+
+ /* UID not found, ignore this message */
+ else fseek (srtcf,((fromlen ? fromlen + 2 : 0) +
+ (tolen ? tolen + 2 : 0) +
+ (cclen ? cclen + 2 : 0) +
+ (subjlen ? subjlen + 2 : 0) +
+ (msgidlen ? msgidlen + 2 : 0) +
+ (reflen ? reflen + 2 : 0)),
+ SEEK_CUR);
+ continue;
+ }
+ else msg = "expansion";
+ }
+ else msg = "references";
+ }
+ else msg = "message-id";
+ }
+ else msg = "subject";
+ }
+ else msg = "cc";
+ }
+ else msg = "to";
+ }
+ else msg = "from";
+ }
+ else msg = "sentdate";
+ break; /* error somewhere */
+ }
+ if (!t || *t) { /* error detected? */
+ if (t) { /* non-null means bogus record */
+ sprintf (tmp,"Error in %s in mix sortcache record: %.500s",msg,t);
+ MM_LOG (tmp,WARN);
+ }
+ fclose (srtcf); /* either way, must punt */
+ srtcf = NIL;
+ }
+ }
+ if (rdonly && srtcf) { /* can't update if readonly */
+ unlink (LOCAL->sortcache); /* try deleting it */
+ fclose (srtcf); /* so close it and return as if error */
+ srtcf = NIL;
+ }
+ else fchmod (fd,sbuf.st_mode);
+ return srtcf;
+}
+
+/* MIX update and close sortcache
+ * Accepts: MAIL stream
+ * pointer to open FILE (if FILE is NIL, do nothing)
+ * Returns: T on success, NIL on error
+ */
+
+long mix_sortcache_update (MAILSTREAM *stream,FILE **sortcache)
+{
+ FILE *f = *sortcache;
+ long ret = LONGT;
+ if (f) { /* ignore if no file */
+ unsigned long i,j;
+ mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
+ for (i = 1; (i <= stream->nmsgs) &&
+ !((SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE))->dirty; ++i);
+ if (i <= stream->nmsgs) { /* only update if some entry is dirty */
+ rewind (f); /* let's start at the very beginning */
+ /* write sequence */
+ fprintf (f,SEQFMT,LOCAL->sortcacheseq = mix_modseq(LOCAL->sortcacheseq));
+ for (i = 1; ret && (i <= stream->nmsgs); ++i) {
+ MESSAGECACHE *elt = mail_elt (stream,i);
+ SORTCACHE *s = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE);
+ STRINGLIST *sl;
+ s->dirty = NIL; /* no longer dirty */
+ if (sl = s->references) /* count length of references */
+ for (j = 1; sl && sl->text.data; sl = sl->next)
+ j += 10 + sl->text.size;
+ else j = 0; /* no references yet */
+ fprintf (f,SCRFMT,elt->private.uid,s->date,
+ s->from ? strlen (s->from) + 1 : 0,
+ s->to ? strlen (s->to) + 1 : 0,s->cc ? strlen (s->cc) + 1 : 0,
+ s->refwd ? 'R' : ' ',s->subject ? strlen (s->subject) + 1: 0,
+ s->message_id ? strlen (s->message_id) + 1 : 0,j);
+ if (s->from) fprintf (f,"F%s\015\012",s->from);
+ if (s->to) fprintf (f,"T%s\015\012",s->to);
+ if (s->cc) fprintf (f,"C%s\015\012",s->cc);
+ if (s->subject) fprintf (f,"S%s\015\012",s->subject);
+ if (s->message_id) fprintf (f,"M%s\015\012",s->message_id);
+ if (j) { /* any references to write? */
+ fputc ('R',f); /* yes, do so */
+ for (sl = s->references; sl && sl->text.data; sl = sl->next)
+ fprintf (f,"%08lx:%s:",sl->text.size,sl->text.data);
+ fputs ("\015\012",f);
+ }
+ if (ferror (f)) {
+ MM_LOG ("Error updating mix sortcache file",WARN);
+ ret = NIL;
+ }
+ }
+ if (ret && fflush (f)) {
+ MM_LOG ("Error flushing mix sortcache file",WARN);
+ ret = NIL;
+ }
+ if (ret) ftruncate (fileno (f),ftell (f));
+ }
+ if (fclose (f)) {
+ MM_LOG ("Error closing mix sortcache file",WARN);
+ ret = NIL;
+ }
+ }
+ return ret;
+}
+
+/* MIX generic file routines */
+
+/* MIX read record
+ * Accepts: open FILE
+ * buffer
+ * buffer length
+ * record type
+ * Returns: buffer if success, else NIL (zero-length buffer means EOF)
+ */
+
+char *mix_read_record (FILE *f,char *buf,unsigned long buflen,char *type)
+{
+ char *s,tmp[MAILTMPLEN];
+ /* ensure string tied off */
+ buf[buflen-2] = buf[buflen-1] = '\0';
+ while (fgets (buf,buflen-1,f)) {
+ if (s = strchr (buf,'\012')) {
+ if ((s != buf) && (s[-1] == '\015')) --s;
+ *s = '\0'; /* tie off buffer */
+ if (s != buf) return buf; /* return if non-empty buffer */
+ sprintf (tmp,"Empty mix %s record",type);
+ MM_LOG (tmp,WARN);
+ }
+ else if (buf[buflen-2]) { /* overlong record is bad news */
+ sprintf (tmp,"Oversize mix %s record: %.512s",type,buf);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else {
+ sprintf (tmp,"Truncated mix %s record: %.512s",type,buf);
+ MM_LOG (tmp,WARN);
+ return buf; /* pass to caller anyway */
+ }
+ }
+ buf[0] = '\0'; /* return empty buffer on EOF */
+ return buf;
+}
+
+/* MIX read sequence record
+ * Accepts: open FILE
+ * Returns: sequence value, or NIL if failure
+ */
+
+unsigned long mix_read_sequence (FILE *f)
+{
+ unsigned long ret;
+ char *s,tmp[MAILTMPLEN];
+ if (!mix_read_record (f,tmp,MAILTMPLEN-1,"sequence")) return NIL;
+ switch (tmp[0]) { /* examine record */
+ case '\0': /* end of file */
+ ret = 1; /* start a new sequence regime */
+ break;
+ case 'S': /* sequence record */
+ if (isxdigit (tmp[1])) { /* must be followed by hex value */
+ ret = strtoul (tmp+1,&s,16);
+ if (!*s) break; /* and nothing more */
+ }
+ /* drop into default case */
+ default: /* anything else is an error */
+ return NIL; /* return error */
+ }
+ return ret;
+}
+
+/* MIX internal routines */
+
+
+/* MIX mail build directory name
+ * Accepts: destination string
+ * source
+ * Returns: destination or empty string if error
+ */
+
+char *mix_dir (char *dst,char *name)
+{
+ char *s;
+ /* empty string if mailboxfile fails */
+ if (!mailboxfile (dst,name)) *dst = '\0';
+ /* driver-selected INBOX */
+ else if (!*dst) mailboxfile (dst,"~/INBOX");
+ /* tie off unnecessary trailing / */
+ else if ((s = strrchr (dst,'/')) && !s[1]) *s = '\0';
+ return dst;
+}
+
+
+/* MIX mail build file name
+ * Accepts: destination string
+ * directory name
+ * file name
+ * Returns: destination
+ */
+
+char *mix_file (char *dst,char *dir,char *name)
+{
+ sprintf (dst,"%.500s/%.80s%.80s",dir,MIXNAME,name);
+ return dst;
+}
+
+
+/* MIX mail build file name from data file number
+ * Accepts: destination string
+ * directory name
+ * data file number
+ * Returns: destination
+ */
+
+char *mix_file_data (char *dst,char *dir,unsigned long data)
+{
+ char tmp[MAILTMPLEN];
+ if (data) sprintf (tmp,"%08lx",data);
+ else tmp[0] = '\0'; /* compatibility with experimental version */
+ return mix_file (dst,dir,tmp);
+}
+
+/* MIX mail get new modseq
+ * Accepts: old modseq
+ * Returns: new modseq value
+ */
+
+unsigned long mix_modseq (unsigned long oldseq)
+{
+ /* normally time now */
+ unsigned long ret = (unsigned long) time (NIL);
+ /* ensure that modseq doesn't go backwards */
+ if (ret <= oldseq) ret = oldseq + 1;
+ return ret;
+}
diff --git a/imap/src/osdep/amiga/mkauths b/imap/src/osdep/amiga/mkauths
new file mode 100755
index 00000000..8e9cc0cf
--- /dev/null
+++ b/imap/src/osdep/amiga/mkauths
@@ -0,0 +1,40 @@
+#!/bin/sh
+# ========================================================================
+# Copyright 1988-2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# ========================================================================
+
+# Program: Authenticator Linkage Generator
+#
+# Author: Mark Crispin
+# Networks and Distributed Computing
+# Computing & Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, WA 98195
+# Internet: MRC@CAC.Washington.EDU
+#
+# Date: 5 December 1995
+# Last Edited: 30 August 2006
+
+# Erase old authenticators list
+rm -f auths.c
+touch auths.c
+
+# Now define the new list
+for authenticator
+ do
+ if [ -f Makefile."$authenticator" ]; then
+ make -f Makefile."$authenticator" `cat SPECIALS`
+ fi
+ echo "extern AUTHENTICATOR auth_"$authenticator";" >> linkage.h
+ echo " auth_link (&auth_"$authenticator"); /* link in the $authenticator authenticator */" | cat >> linkage.c
+ echo "#include \"auth_"$authenticator".c\"" >> auths.c
+done
diff --git a/imap/src/osdep/amiga/mmdf.c b/imap/src/osdep/amiga/mmdf.c
new file mode 100644
index 00000000..e962434e
--- /dev/null
+++ b/imap/src/osdep/amiga/mmdf.c
@@ -0,0 +1,2549 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MMDF mail routines
+ *
+ * Author: Mark Crispin
+ * UW Technology
+ * University of Washington
+ * Seattle, WA 98195
+ * Internet: MRC@Washington.EDU
+ *
+ * Date: 20 December 1989
+ * Last Edited: 27 March 2008
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <signal.h>
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <sys/stat.h>
+#include "pseudo.h"
+#include "fdstring.h"
+#include "misc.h"
+#include "dummy.h"
+
+/* Supposedly, this page has everything the MMDF driver needs to know about
+ * the MMDF delimiter. By changing these macros, the MMDF driver should
+ * change with it. Note that if you change the length of MMDFHDRTXT you
+ * also need to change the ISMMDF and RETIFMMDFWRD macros to reflect the new
+ * size.
+ */
+
+
+/* Useful MMDF constants */
+
+#define MMDFCHR '\01' /* MMDF character */
+#define MMDFCHRS 0x01010101 /* MMDF header character spread in a word */
+ /* MMDF header text */
+#define MMDFHDRTXT "\01\01\01\01\n"
+ /* length of MMDF header text */
+#define MMDFHDRLEN (sizeof (MMDFHDRTXT) - 1)
+
+
+/* Validate MMDF header
+ * Accepts: pointer to candidate string to validate as an MMDF header
+ * Returns: T if valid; else NIL
+ */
+
+#define ISMMDF(s) \
+ ((*(s) == MMDFCHR) && ((s)[1] == MMDFCHR) && ((s)[2] == MMDFCHR) && \
+ ((s)[3] == MMDFCHR) && ((s)[4] == '\n'))
+
+
+/* Return if a 32-bit word has the start of an MMDF header
+ * Accepts: pointer to word of four bytes to validate as an MMDF header
+ * Returns: pointer to MMDF header, else proceeds
+ */
+
+#define RETIFMMDFWRD(s) { \
+ if (s[3] == MMDFCHR) { \
+ if ((s[4] == MMDFCHR) && (s[5] == MMDFCHR) && (s[6] == MMDFCHR) && \
+ (s[7] == '\n')) return s + 3; \
+ else if (s[2] == MMDFCHR) { \
+ if ((s[4] == MMDFCHR) && (s[5] == MMDFCHR) && (s[6] == '\n')) \
+ return s + 2; \
+ else if (s[1] == MMDFCHR) { \
+ if ((s[4] == MMDFCHR) && (s[5] == '\n')) return s + 1; \
+ else if ((*s == MMDFCHR) && (s[4] == '\n')) return s; \
+ } \
+ } \
+ } \
+}
+
+/* Validate line
+ * Accepts: pointer to candidate string to validate as a From header
+ * return pointer to end of date/time field
+ * return pointer to offset from t of time (hours of ``mmm dd hh:mm'')
+ * return pointer to offset from t of time zone (if non-zero)
+ * Returns: t,ti,zn set if valid From string, else ti is NIL
+ */
+
+#define VALID(s,x,ti,zn) { \
+ ti = 0; \
+ if ((*s == 'F') && (s[1] == 'r') && (s[2] == 'o') && (s[3] == 'm') && \
+ (s[4] == ' ')) { \
+ for (x = s + 5; *x && *x != '\n'; x++); \
+ if (*x) { \
+ if (x - s >= 41) { \
+ for (zn = -1; x[zn] != ' '; zn--); \
+ if ((x[zn-1] == 'm') && (x[zn-2] == 'o') && (x[zn-3] == 'r') && \
+ (x[zn-4] == 'f') && (x[zn-5] == ' ') && (x[zn-6] == 'e') && \
+ (x[zn-7] == 't') && (x[zn-8] == 'o') && (x[zn-9] == 'm') && \
+ (x[zn-10] == 'e') && (x[zn-11] == 'r') && (x[zn-12] == ' '))\
+ x += zn - 12; \
+ } \
+ if (x - s >= 27) { \
+ if (x[-5] == ' ') { \
+ if (x[-8] == ':') zn = 0,ti = -5; \
+ else if (x[-9] == ' ') ti = zn = -9; \
+ else if ((x[-11] == ' ') && ((x[-10]=='+') || (x[-10]=='-'))) \
+ ti = zn = -11; \
+ } \
+ else if (x[-4] == ' ') { \
+ if (x[-9] == ' ') zn = -4,ti = -9; \
+ } \
+ else if (x[-6] == ' ') { \
+ if ((x[-11] == ' ') && ((x[-5] == '+') || (x[-5] == '-'))) \
+ zn = -6,ti = -11; \
+ } \
+ if (ti && !((x[ti - 3] == ':') && \
+ (x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') && \
+ (x[ti - 3] == ' ') && (x[ti - 7] == ' ') && \
+ (x[ti - 11] == ' '))) ti = 0; \
+ } \
+ } \
+ } \
+}
+
+/* You are not expected to understand this macro, but read the next page if
+ * you are not faint of heart.
+ *
+ * Known formats to the VALID macro are:
+ * From user Wed Dec 2 05:53 1992
+ * BSD From user Wed Dec 2 05:53:22 1992
+ * SysV From user Wed Dec 2 05:53 PST 1992
+ * rn From user Wed Dec 2 05:53:22 PST 1992
+ * From user Wed Dec 2 05:53 -0700 1992
+ * emacs From user Wed Dec 2 05:53:22 -0700 1992
+ * From user Wed Dec 2 05:53 1992 PST
+ * From user Wed Dec 2 05:53:22 1992 PST
+ * From user Wed Dec 2 05:53 1992 -0700
+ * Solaris From user Wed Dec 2 05:53:22 1992 -0700
+ *
+ * Plus all of the above with `` remote from xxx'' after it. Thank you very
+ * much, smail and Solaris, for making my life considerably more complicated.
+ */
+
+/*
+ * What? You want to understand the VALID macro anyway? Alright, since you
+ * insist. Actually, it isn't really all that difficult, provided that you
+ * take it step by step.
+ *
+ * Line 1 Initializes the return ti value to failure (0);
+ * Lines 2-3 Validates that the 1st-5th characters are ``From ''.
+ * Lines 4-5 Validates that there is an end of line and points x at it.
+ * Lines 6-13 First checks to see if the line is at least 41 characters long.
+ * If so, it scans backwards to find the rightmost space. From
+ * that point, it scans backwards to see if the string matches
+ * `` remote from''. If so, it sets x to point to the space at
+ * the start of the string.
+ * Line 14 Makes sure that there are at least 27 characters in the line.
+ * Lines 15-20 Checks if the date/time ends with the year (there is a space
+ * five characters back). If there is a colon three characters
+ * further back, there is no timezone field, so zn is set to 0
+ * and ti is set in front of the year. Otherwise, there must
+ * either to be a space four characters back for a three-letter
+ * timezone, or a space six characters back followed by a + or -
+ * for a numeric timezone; in either case, zn and ti become the
+ * offset of the space immediately before it.
+ * Lines 21-23 Are the failure case for line 14. If there is a space four
+ * characters back, it is a three-letter timezone; there must be a
+ * space for the year nine characters back. zn is the zone
+ * offset; ti is the offset of the space.
+ * Lines 24-27 Are the failure case for line 20. If there is a space six
+ * characters back, it is a numeric timezone; there must be a
+ * space eleven characters back and a + or - five characters back.
+ * zn is the zone offset; ti is the offset of the space.
+ * Line 28-31 If ti is valid, make sure that the string before ti is of the
+ * form www mmm dd hh:mm or www mmm dd hh:mm:ss, otherwise
+ * invalidate ti. There must be a colon three characters back
+ * and a space six or nine characters back (depending upon
+ * whether or not the character six characters back is a colon).
+ * There must be a space three characters further back (in front
+ * of the day), one seven characters back (in front of the month),
+ * and one eleven characters back (in front of the day of week).
+ * ti is set to be the offset of the space before the time.
+ *
+ * Why a macro? It gets invoked a *lot* in a tight loop. On some of the
+ * newer pipelined machines it is faster being open-coded than it would be if
+ * subroutines are called.
+ *
+ * Why does it scan backwards from the end of the line, instead of doing the
+ * much easier forward scan? There is no deterministic way to parse the
+ * ``user'' field, because it may contain unquoted spaces! Yes, I tested it to
+ * see if unquoted spaces were possible. They are, and I've encountered enough
+ * evil mail to be totally unwilling to trust that ``it will never happen''.
+ */
+
+/* Build parameters */
+
+#define KODRETRY 15 /* kiss-of-death retry in seconds */
+#define LOCKTIMEOUT 5 /* lock timeout in minutes */
+
+
+/* MMDF I/O stream local data */
+
+typedef struct mmdf_local {
+ unsigned int dirty : 1; /* disk copy needs updating */
+ unsigned int ddirty : 1; /* double-dirty, ping becomes checkpoint */
+ unsigned int pseudo : 1; /* uses a pseudo message */
+ unsigned int appending : 1; /* don't mark new messages as old */
+ int fd; /* mailbox file descriptor */
+ int ld; /* lock file descriptor */
+ char *lname; /* lock file name */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned long uid; /* current text uid */
+ SIZEDTEXT text; /* current text */
+ unsigned long textlen; /* current text length */
+ char *line; /* returned line */
+ char *linebuf; /* line readin buffer */
+ unsigned long linebuflen; /* current line readin buffer length */
+} MMDFLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MMDFLOCAL *) stream->local)
+
+
+/* MMDF protected file structure */
+
+typedef struct mmdf_file {
+ MAILSTREAM *stream; /* current stream */
+ off_t curpos; /* current file position */
+ off_t protect; /* protected position */
+ off_t filepos; /* current last written file position */
+ char *buf; /* overflow buffer */
+ size_t buflen; /* current overflow buffer length */
+ char *bufpos; /* current buffer position */
+} MMDFFILE;
+
+/* Function prototypes */
+
+DRIVER *mmdf_valid (char *name);
+long mmdf_isvalid (char *name,char *tmp);
+long mmdf_isvalid_fd (int fd,char *tmp);
+void *mmdf_parameters (long function,void *value);
+void mmdf_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void mmdf_list (MAILSTREAM *stream,char *ref,char *pat);
+void mmdf_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mmdf_create (MAILSTREAM *stream,char *mailbox);
+long mmdf_delete (MAILSTREAM *stream,char *mailbox);
+long mmdf_rename (MAILSTREAM *stream,char *old,char *newname);
+MAILSTREAM *mmdf_open (MAILSTREAM *stream);
+void mmdf_close (MAILSTREAM *stream,long options);
+char *mmdf_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long mmdf_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+char *mmdf_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
+ unsigned long *length,long flags);
+void mmdf_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long mmdf_ping (MAILSTREAM *stream);
+void mmdf_check (MAILSTREAM *stream);
+long mmdf_expunge (MAILSTREAM *stream,char *sequence,long options);
+long mmdf_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long mmdf_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+int mmdf_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg);
+int mmdf_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set);
+
+void mmdf_abort (MAILSTREAM *stream);
+char *mmdf_file (char *dst,char *name);
+int mmdf_lock (char *file,int flags,int mode,DOTLOCK *lock,int op);
+void mmdf_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock);
+int mmdf_parse (MAILSTREAM *stream,DOTLOCK *lock,int op);
+char *mmdf_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size);
+unsigned long mmdf_pseudo (MAILSTREAM *stream,char *hdr);
+unsigned long mmdf_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
+ unsigned long uid,long flag);
+long mmdf_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock,
+ long flags);
+long mmdf_extend (MAILSTREAM *stream,unsigned long size);
+void mmdf_write (MMDFFILE *f,char *s,unsigned long i);
+void mmdf_phys_write (MMDFFILE *f,char *buf,size_t size);
+
+/* MMDF mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mmdfdriver = {
+ "mmdf", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_LOCKING|DR_NONEWMAILRONLY|DR_XPOINT,
+ (DRIVER *) NIL, /* next driver */
+ mmdf_valid, /* mailbox is valid for us */
+ mmdf_parameters, /* manipulate parameters */
+ mmdf_scan, /* scan mailboxes */
+ mmdf_list, /* list mailboxes */
+ mmdf_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ mmdf_create, /* create mailbox */
+ mmdf_delete, /* delete mailbox */
+ mmdf_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ mmdf_open, /* open mailbox */
+ mmdf_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mmdf_header, /* fetch message header */
+ mmdf_text, /* fetch message text */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ mmdf_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mmdf_ping, /* ping mailbox to see if still alive */
+ mmdf_check, /* check for new messages */
+ mmdf_expunge, /* expunge deleted messages */
+ mmdf_copy, /* copy messages to another mailbox */
+ mmdf_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mmdfproto = {&mmdfdriver};
+
+char *mmdfhdr = MMDFHDRTXT; /* MMDF header */
+
+/* MMDF mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mmdf_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return mmdf_isvalid (name,tmp) ? &mmdfdriver : NIL;
+}
+
+
+/* MMDF mail test for valid mailbox name
+ * Accepts: mailbox name
+ * scratch buffer
+ * Returns: T if valid, NIL otherwise
+ */
+
+long mmdf_isvalid (char *name,char *tmp)
+{
+ int fd;
+ int ret = NIL;
+ char *t,file[MAILTMPLEN];
+ struct stat sbuf;
+ time_t tp[2];
+ errno = EINVAL; /* assume invalid argument */
+ /* must be non-empty file */
+ if ((t = dummy_file (file,name)) && !stat (t,&sbuf)) {
+ if (!sbuf.st_size)errno = 0;/* empty file */
+ else if ((fd = open (file,O_RDONLY,NIL)) >= 0) {
+ /* error -1 for invalid format */
+ if (!(ret = mmdf_isvalid_fd (fd,tmp))) errno = -1;
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) {
+ tp[0] = sbuf.st_atime; /* preserve atime and mtime */
+ tp[1] = sbuf.st_mtime;
+ utime (file,tp); /* set the times */
+ }
+ }
+ }
+ return ret; /* return what we should */
+}
+
+/* MMDF mail test for valid mailbox
+ * Accepts: file descriptor
+ * scratch buffer
+ * Returns: T if valid, NIL otherwise
+ */
+
+long mmdf_isvalid_fd (int fd,char *tmp)
+{
+ int ret = NIL;
+ memset (tmp,'\0',MAILTMPLEN);
+ if (read (fd,tmp,MAILTMPLEN-1) >= 0) ret = ISMMDF (tmp) ? T : NIL;
+ return ret; /* return what we should */
+}
+
+
+/* MMDF manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mmdf_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = dummy_file ((char *) value,"INBOX");
+ break;
+ }
+ return ret;
+}
+
+/* MMDF mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mmdf_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MMDF mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mmdf_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MMDF mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mmdf_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MMDF mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mmdf_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN];
+ long ret = NIL;
+ int i,fd;
+ time_t ti = time (0);
+ if (!(s = dummy_file (mbx,mailbox))) {
+ sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
+ MM_LOG (tmp,ERROR);
+ }
+ /* create underlying file */
+ else if (dummy_create_path (stream,s,get_dir_protection (mailbox))) {
+ /* done if dir-only or whiner */
+ if (((s = strrchr (s,'/')) && !s[1]) ||
+ mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) ret = T;
+ else if ((fd = open (mbx,O_WRONLY,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0) {
+ sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else { /* initialize header */
+ memset (tmp,'\0',MAILTMPLEN);
+ sprintf (tmp,"%sFrom %s %sDate: ",mmdfhdr,pseudo_from,ctime (&ti));
+ rfc822_date (s = tmp + strlen (tmp));
+ sprintf (s += strlen (s), /* write the pseudo-header */
+ "\nFrom: %s <%s@%s>\nSubject: %s\nX-IMAP: %010lu 0000000000",
+ pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
+ (unsigned long) ti);
+ for (i = 0; i < NUSERFLAGS; ++i) if (default_user_flag (i))
+ sprintf (s += strlen (s)," %s",default_user_flag (i));
+ sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n%s",pseudo_msg,mmdfhdr);
+ if (write (fd,tmp,strlen (tmp)) > 0) ret = T;
+ else {
+ sprintf (tmp,"Can't initialize mailbox node %.80s: %s",mbx,
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ close (fd); /* close file */
+ }
+ }
+ /* set proper protections */
+ return ret ? set_mbx_protections (mailbox,mbx) : NIL;
+}
+
+/* MMDF mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mmdf_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return mmdf_rename (stream,mailbox,NIL);
+}
+
+
+/* MMDF mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long mmdf_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = NIL;
+ char c,*s = NIL;
+ char tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ DOTLOCK lockx;
+ int fd,ld;
+ long i;
+ struct stat sbuf;
+ MM_CRITICAL (stream); /* get the c-client lock */
+ if (!dummy_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'/')) && !s[1]))))
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ /* lock out other c-clients */
+ else if ((ld = lockname (lock,file,LOCK_EX|LOCK_NB,&i)) < 0)
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+
+ else {
+ if ((fd = mmdf_lock (file,O_RDWR,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lockx,LOCK_EX)) < 0)
+ sprintf (tmp,"Can't lock mailbox %.80s: %s",old,strerror (errno));
+ else {
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if (s = strrchr (s,'/')) {
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp,get_dir_protection (newname))) {
+ mmdf_unlock (fd,NIL,&lockx);
+ mmdf_unlock (ld,NIL,NIL);
+ unlink (lock);
+ MM_NOCRITICAL (stream);
+ return ret; /* return success or failure */
+ }
+ *s = c; /* restore full name */
+ }
+ if (rename (file,tmp))
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ else ret = T; /* set success */
+ }
+ else if (unlink (file))
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ else ret = T; /* set success */
+ mmdf_unlock (fd,NIL,&lockx);
+ }
+ mmdf_unlock (ld,NIL,NIL); /* flush the lock */
+ unlink (lock);
+ }
+ MM_NOCRITICAL (stream); /* no longer critical */
+ if (!ret) MM_LOG (tmp,ERROR); /* log error */
+ return ret; /* return success or failure */
+}
+
+/* MMDF mail open
+ * Accepts: Stream to open
+ * Returns: Stream on success, NIL on failure
+ */
+
+MAILSTREAM *mmdf_open (MAILSTREAM *stream)
+{
+ long i;
+ int fd;
+ char tmp[MAILTMPLEN];
+ DOTLOCK lock;
+ long retry;
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&mmdfproto);
+ retry = stream->silent ? 1 : KODRETRY;
+ if (stream->local) fatal ("mmdf recycle stream");
+ stream->local = memset (fs_get (sizeof (MMDFLOCAL)),0,sizeof (MMDFLOCAL));
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ /* canonicalize the stream mailbox name */
+ if (!dummy_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* flush old name */
+ fs_give ((void **) &stream->mailbox);
+ /* save canonical name */
+ stream->mailbox = cpystr (tmp);
+ LOCAL->fd = LOCAL->ld = -1; /* no file or state locking yet */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE);
+ LOCAL->text.size = CHUNKSIZE - 1;
+ LOCAL->linebuf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->linebuflen = CHUNKSIZE - 1;
+ stream->sequence++; /* bump sequence number */
+
+ /* make lock for read/write access */
+ if (!stream->rdonly) while (retry) {
+ /* try to lock file */
+ if ((fd = lockname (tmp,stream->mailbox,LOCK_EX|LOCK_NB,&i)) < 0) {
+ /* suppressing kiss-of-death? */
+ if (stream->nokod) retry = 0;
+ /* no, first time through? */
+ else if (retry-- == KODRETRY) {
+ /* learned other guy's PID and can signal? */
+ if (i && !kill ((int) i,SIGUSR2)) {
+ sprintf (tmp,"Trying to get mailbox lock from process %ld",i);
+ MM_LOG (tmp,WARN);
+ }
+ else retry = 0; /* give up */
+ }
+ if (!stream->silent) { /* nothing if silent stream */
+ if (retry) sleep (1); /* wait a second before trying again */
+ else MM_LOG ("Mailbox is open by another process, access is readonly",
+ WARN);
+ }
+ }
+ else { /* got the lock, nobody else can alter state */
+ LOCAL->ld = fd; /* note lock's fd and name */
+ LOCAL->lname = cpystr (tmp);
+ /* make sure mode OK (don't use fchmod()) */
+ chmod (LOCAL->lname,(long) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
+ if (stream->silent) i = 0;/* silent streams won't accept KOD */
+ else { /* note our PID in the lock */
+ sprintf (tmp,"%d",getpid ());
+ write (fd,tmp,(i = strlen (tmp))+1);
+ }
+ ftruncate (fd,i); /* make sure tied off */
+ fsync (fd); /* make sure it's available */
+ retry = 0; /* no more need to try */
+ }
+ }
+
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ /* will we be able to get write access? */
+ if ((LOCAL->ld >= 0) && access (stream->mailbox,W_OK) && (errno == EACCES)) {
+ MM_LOG ("Can't get write access to mailbox, access is readonly",WARN);
+ flock (LOCAL->ld,LOCK_UN); /* release the lock */
+ close (LOCAL->ld); /* close the lock file */
+ LOCAL->ld = -1; /* no more lock fd */
+ unlink (LOCAL->lname); /* delete it */
+ }
+ /* reset UID validity */
+ stream->uid_validity = stream->uid_last = 0;
+ if (stream->silent && !stream->rdonly && (LOCAL->ld < 0))
+ mmdf_abort (stream); /* abort if can't get RW silent stream */
+ /* parse mailbox */
+ else if (mmdf_parse (stream,&lock,LOCK_SH)) {
+ mmdf_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream); /* done with critical */
+ }
+ if (!LOCAL) return NIL; /* failure if stream died */
+ /* make sure upper level knows readonly */
+ stream->rdonly = (LOCAL->ld < 0);
+ /* notify about empty mailbox */
+ if (!(stream->nmsgs || stream->silent)) MM_LOG ("Mailbox is empty",NIL);
+ if (!stream->rdonly) { /* flags stick if readwrite */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft = T;
+ if (!stream->uid_nosticky) {/* users with lives get permanent keywords */
+ stream->perm_user_flags = 0xffffffff;
+ /* and maybe can create them too! */
+ stream->kwd_create = stream->user_flags[NUSERFLAGS-1] ? NIL : T;
+ }
+ }
+ return stream; /* return stream alive to caller */
+}
+
+
+/* MMDF mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mmdf_close (MAILSTREAM *stream,long options)
+{
+ int silent = stream->silent;
+ stream->silent = T; /* go silent */
+ /* expunge if requested */
+ if (options & CL_EXPUNGE) mmdf_expunge (stream,NIL,NIL);
+ /* else dump final checkpoint */
+ else if (LOCAL->dirty) mmdf_check (stream);
+ stream->silent = silent; /* restore old silence state */
+ mmdf_abort (stream); /* now punt the file and local data */
+}
+
+/* MMDF mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+ /* lines to filter from header */
+static STRINGLIST *mmdf_hlines = NIL;
+
+char *mmdf_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned char *s,*t,*tl;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get cache */
+ if (!mmdf_hlines) { /* once only code */
+ STRINGLIST *lines = mmdf_hlines = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "Status"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-Status"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-Keywords"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-UID"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-IMAP"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-IMAPbase"));
+ }
+ /* go to header position */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.msg.header.offset,L_SET);
+
+ if (flags & FT_INTERNAL) { /* initial data OK? */
+ if (elt->private.msg.header.text.size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
+ elt->private.msg.header.text.size) + 1);
+ }
+ /* read message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.header.text.size);
+ /* got text, tie off string */
+ LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0';
+ /* squeeze out CRs (in case from PC) */
+ for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
+ if (*t != '\r') *s++ = *t;
+ *s = '\0';
+ *length = s - LOCAL->buf; /* adjust length */
+ }
+ else { /* need to make a CRLF version */
+ read (LOCAL->fd,s = (char *) fs_get (elt->private.msg.header.text.size+1),
+ elt->private.msg.header.text.size);
+ /* tie off string, and convert to CRLF */
+ s[elt->private.msg.header.text.size] = '\0';
+ *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s,
+ elt->private.msg.header.text.size);
+ fs_give ((void **) &s); /* free readin buffer */
+ /* squeeze out spurious CRs */
+ for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
+ if ((*t != '\r') || (t[1] == '\n')) *s++ = *t;
+ *s = '\0';
+ *length = s - LOCAL->buf; /* adjust length */
+ }
+ *length = mail_filter (LOCAL->buf,*length,mmdf_hlines,FT_NOT);
+ return (char *) LOCAL->buf; /* return processed copy */
+}
+
+/* MMDF mail fetch message text
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL if failure
+ */
+
+long mmdf_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ char *s;
+ unsigned long i;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);/* get cache element */
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ /* mark message seen and dirty */
+ elt->seen = elt->private.dirty = LOCAL->dirty = T;
+ MM_FLAGS (stream,msgno);
+ }
+ s = mmdf_text_work (stream,elt,&i,flags);
+ INIT (bs,mail_string,s,i); /* set up stringstruct */
+ return T; /* success */
+}
+
+/* MMDF mail fetch message text worker routine
+ * Accepts: MAIL stream
+ * message cache element
+ * pointer to returned header text length
+ * option flags
+ */
+
+char *mmdf_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
+ unsigned long *length,long flags)
+{
+ FDDATA d;
+ STRING bs;
+ unsigned char c,*s,*t,*tl,tmp[CHUNKSIZE];
+ /* go to text position */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.msg.text.offset,L_SET);
+ if (flags & FT_INTERNAL) { /* initial data OK? */
+ if (elt->private.msg.text.text.size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
+ elt->private.msg.text.text.size) + 1);
+ }
+ /* read message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size);
+ /* got text, tie off string */
+ LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0';
+ /* squeeze out CRs (in case from PC) */
+ for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
+ if (*t != '\r') *s++ = *t;
+ *s = '\0';
+ *length = s - LOCAL->buf; /* adjust length */
+ return (char *) LOCAL->buf;
+ }
+
+ /* have it cached already? */
+ if (elt->private.uid != LOCAL->uid) {
+ /* not cached, cache it now */
+ LOCAL->uid = elt->private.uid;
+ /* is buffer big enough? */
+ if (elt->rfc822_size > LOCAL->text.size) {
+ /* excessively conservative, but the right thing is too hard to do */
+ fs_give ((void **) &LOCAL->text.data);
+ LOCAL->text.data = (unsigned char *)
+ fs_get ((LOCAL->text.size = elt->rfc822_size) + 1);
+ }
+ d.fd = LOCAL->fd; /* yes, set up file descriptor */
+ d.pos = elt->private.special.offset + elt->private.msg.text.offset;
+ d.chunk = tmp; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* file chunk size */
+ INIT (&bs,fd_string,&d,elt->private.msg.text.text.size);
+ for (s = (char *) LOCAL->text.data; SIZE (&bs);) switch (c = SNX (&bs)) {
+ case '\r': /* carriage return seen */
+ break;
+ case '\n':
+ *s++ = '\r'; /* insert a CR */
+ default:
+ *s++ = c; /* copy characters */
+ }
+ *s = '\0'; /* tie off buffer */
+ /* calculate length of cached data */
+ LOCAL->textlen = s - LOCAL->text.data;
+ }
+ *length = LOCAL->textlen; /* return from cache */
+ return (char *) LOCAL->text.data;
+}
+
+/* MMDF per-message modify flag
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void mmdf_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ /* only after finishing */
+ if (elt->valid) elt->private.dirty = LOCAL->dirty = T;
+}
+
+
+/* MMDF mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long mmdf_ping (MAILSTREAM *stream)
+{
+ DOTLOCK lock;
+ struct stat sbuf;
+ long reparse;
+ /* big no-op if not readwrite */
+ if (LOCAL && (LOCAL->ld >= 0) && !stream->lock) {
+ if (stream->rdonly) { /* does he want to give up readwrite? */
+ /* checkpoint if we changed something */
+ if (LOCAL->dirty) mmdf_check (stream);
+ flock (LOCAL->ld,LOCK_UN);/* release readwrite lock */
+ close (LOCAL->ld); /* close the readwrite lock file */
+ LOCAL->ld = -1; /* no more readwrite lock fd */
+ unlink (LOCAL->lname); /* delete the readwrite lock file */
+ }
+ else { /* see if need to reparse */
+ if (!(reparse = (long) mail_parameters (NIL,GET_NETFSSTATBUG,NIL))) {
+ /* get current mailbox size */
+ if (LOCAL->fd >= 0) fstat (LOCAL->fd,&sbuf);
+ else if (stat (stream->mailbox,&sbuf)) {
+ sprintf (LOCAL->buf,"Mailbox stat failed, aborted: %s",
+ strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ mmdf_abort (stream);
+ return NIL;
+ }
+ reparse = (sbuf.st_size != LOCAL->filesize);
+ }
+ /* parse if mailbox changed */
+ if ((LOCAL->ddirty || reparse) && mmdf_parse (stream,&lock,LOCK_EX)) {
+ /* force checkpoint if double-dirty */
+ if (LOCAL->ddirty) mmdf_rewrite (stream,NIL,&lock,NIL);
+ /* unlock mailbox */
+ else mmdf_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* and stream */
+ /* done with critical */
+ MM_NOCRITICAL (stream);
+ }
+ }
+ }
+ return LOCAL ? LONGT : NIL; /* return if still alive */
+}
+
+/* MMDF mail check mailbox
+ * Accepts: MAIL stream
+ */
+
+void mmdf_check (MAILSTREAM *stream)
+{
+ DOTLOCK lock;
+ /* parse and lock mailbox */
+ if (LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
+ mmdf_parse (stream,&lock,LOCK_EX)) {
+ /* any unsaved changes? */
+ if (LOCAL->dirty && mmdf_rewrite (stream,NIL,&lock,NIL)) {
+ if (!stream->silent) MM_LOG ("Checkpoint completed",NIL);
+ }
+ /* no checkpoint needed, just unlock */
+ else mmdf_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* unlock the stream */
+ MM_NOCRITICAL (stream); /* done with critical */
+ }
+}
+
+
+/* MMDF mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long mmdf_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ unsigned long i;
+ DOTLOCK lock;
+ char *msg = NIL;
+ if (ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
+ mmdf_parse (stream,&lock,LOCK_EX)) {
+ /* check expunged messages if not dirty */
+ for (i = 1; !LOCAL->dirty && (i <= stream->nmsgs); i++) {
+ MESSAGECACHE *elt = mail_elt (stream,i);
+ if (mail_elt (stream,i)->deleted) LOCAL->dirty = T;
+ }
+ if (!LOCAL->dirty) { /* not dirty and no expunged messages */
+ mmdf_unlock (LOCAL->fd,stream,&lock);
+ msg = "No messages deleted, so no update needed";
+ }
+ else if (mmdf_rewrite (stream,&i,&lock,sequence ? LONGT : NIL)) {
+ if (i) sprintf (msg = LOCAL->buf,"Expunged %lu messages",i);
+ else msg = "Mailbox checkpointed, but no messages expunged";
+ }
+ /* rewrite failed */
+ else mmdf_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* unlock the stream */
+ MM_NOCRITICAL (stream); /* done with critical */
+ if (msg && !stream->silent) MM_LOG (msg,NIL);
+ }
+ else if (!stream->silent)
+ MM_LOG ("Expunge ignored on readonly mailbox",WARN);
+ return ret;
+}
+
+/* MMDF mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long mmdf_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ int fd;
+ char *s,file[MAILTMPLEN];
+ DOTLOCK lock;
+ time_t tp[2];
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ long ret = T;
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ copyuid_t cu = (copyuid_t) (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ?
+ NIL : mail_parameters (NIL,GET_COPYUID,NIL));
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ MAILSTREAM *tstream = NIL;
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* make sure destination is valid */
+ if (!(mmdf_valid (mailbox) || !errno))
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ }
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ mmdf_create (NIL,"INBOX");/* create empty INBOX */
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid MMDF-format mailbox name: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a MMDF-format mailbox: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+
+ /* try to open rewrite for UIDPLUS */
+ if ((tstream = mail_open_work (&mmdfdriver,NIL,mailbox,
+ OP_SILENT|OP_NOKOD)) && tstream->rdonly)
+ tstream = mail_close (tstream);
+ if (cu && !tstream) { /* wanted a COPYUID? */
+ sprintf (LOCAL->buf,"Unable to write-open mailbox for COPYUID: %.80s",
+ mailbox);
+ MM_LOG (LOCAL->buf,WARN);
+ cu = NIL; /* don't try to do COPYUID */
+ }
+ LOCAL->buf[0] = '\0';
+ MM_CRITICAL (stream); /* go critical */
+ if ((fd = mmdf_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lock,LOCK_EX)) < 0) {
+ MM_NOCRITICAL (stream); /* done with critical */
+ sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR); /* log the error */
+ return NIL; /* failed */
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ /* write all requested messages to mailbox */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
+ if (write (fd,LOCAL->buf,elt->private.special.text.size) < 0) ret = NIL;
+ else { /* internal header succeeded */
+ s = mmdf_header (stream,i,&j,FT_INTERNAL);
+ /* header size, sans trailing newline */
+ if (j && (s[j - 2] == '\n')) j--;
+ if (write (fd,s,j) < 0) ret = NIL;
+ else { /* message header succeeded */
+ j = tstream ? /* write UIDPLUS data if have readwrite */
+ mmdf_xstatus (stream,LOCAL->buf,elt,++(tstream->uid_last),LONGT) :
+ mmdf_xstatus (stream,LOCAL->buf,elt,NIL,NIL);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ else { /* message status succeeded */
+ s = mmdf_text_work (stream,elt,&j,FT_INTERNAL);
+ if ((write (fd,s,j) < 0) || (write (fd,mmdfhdr,MMDFHDRLEN) < 0))
+ ret = NIL;
+ else if (cu) { /* need to pass back new UID? */
+ mail_append_set (source,mail_uid (stream,i));
+ mail_append_set (dest,tstream->uid_last);
+ }
+ }
+ }
+ }
+ }
+
+ if (!ret || fsync (fd)) { /* force out the update */
+ sprintf (LOCAL->buf,"Message copy failed: %s",strerror (errno));
+ ftruncate (fd,sbuf.st_size);
+ ret = NIL;
+ }
+ /* force UIDVALIDITY assignment now */
+ if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0);
+ /* return sets if doing COPYUID */
+ if (cu && ret) (*cu) (stream,mailbox,tstream->uid_validity,source,dest);
+ else { /* flush any sets we may have built */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ tp[1] = time (0); /* set mtime to now */
+ if (ret) tp[0] = tp[1] - 1; /* set atime to now-1 if successful copy */
+ else tp[0] = /* else preserve \Marked status */
+ ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
+ sbuf.st_atime : tp[1];
+ utime (file,tp); /* set the times */
+ mmdf_unlock (fd,NIL,&lock); /* unlock and close mailbox */
+ if (tstream) { /* update last UID if we can */
+ MMDFLOCAL *local = (MMDFLOCAL *) tstream->local;
+ local->dirty = T; /* do a rewrite */
+ local->appending = T; /* but not at the cost of marking as old */
+ tstream = mail_close (tstream);
+ }
+ /* log the error */
+ if (!ret) MM_LOG (LOCAL->buf,ERROR);
+ /* delete if requested message */
+ else if (options & CP_MOVE) for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence)
+ elt->deleted = elt->private.dirty = LOCAL->dirty = T;
+ MM_NOCRITICAL (stream); /* release critical */
+ return ret;
+}
+
+/* MMDF mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+#define BUFLEN 8*MAILTMPLEN
+
+long mmdf_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd;
+ unsigned long i;
+ char *flags,*date,buf[BUFLEN],tmp[MAILTMPLEN],file[MAILTMPLEN];
+ time_t tp[2];
+ FILE *sf,*df;
+ MESSAGECACHE elt;
+ DOTLOCK lock;
+ STRING *message;
+ unsigned long uidlocation = 0;
+ appenduid_t au = (appenduid_t)
+ (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ? NIL :
+ mail_parameters (NIL,GET_APPENDUID,NIL));
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ long ret = LONGT;
+ MAILSTREAM *tstream = NIL;
+ /* default stream to prototype */
+ if (!stream) { /* stream specified? */
+ stream = &mmdfproto; /* no, default stream to prototype */
+ for (i = 0; i < NUSERFLAGS && stream->user_flags[i]; ++i)
+ fs_give ((void **) &stream->user_flags[i]);
+ }
+ if (!mmdf_valid (mailbox)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ mmdf_create (NIL,"INBOX"); /* create empty INBOX */
+ case 0: /* merely empty file? */
+ tstream = stream;
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid MMDF-format mailbox name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MMDF-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get sniffing stream for keywords */
+ else if (!(tstream = mail_open (NIL,mailbox,
+ OP_READONLY|OP_SILENT|OP_NOKOD|OP_SNIFF))) {
+ sprintf (tmp,"Unable to examine mailbox for APPEND: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+
+ /* get first message */
+ if (!MM_APPEND (af) (tstream,data,&flags,&date,&message)) return NIL;
+ if (!(sf = tmpfile ())) { /* must have scratch file */
+ sprintf (tmp,".%lx.%lx",(unsigned long) time (0),(unsigned long)getpid ());
+ if (!stat (tmp,&sbuf) || !(sf = fopen (tmp,"wb+"))) {
+ sprintf (tmp,"Unable to create scratch file: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ unlink (tmp);
+ }
+ do { /* parse date */
+ if (!date) rfc822_date (date = tmp);
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ }
+ else { /* user wants to suppress time zones? */
+ if (mail_parameters (NIL,GET_NOTIMEZONES,NIL)) {
+ time_t when = mail_longdate (&elt);
+ date = ctime (&when); /* use traditional date */
+ }
+ /* use POSIX-style date */
+ else date = mail_cdate (tmp,&elt);
+ if (!SIZE (message)) MM_LOG ("Append of zero-length message",ERROR);
+ else if (!mmdf_collect_msg (tstream,sf,flags,date,message)) {
+ sprintf (tmp,"Error writing scratch file: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ /* get next message */
+ else if (MM_APPEND (af) (tstream,data,&flags,&date,&message)) continue;
+ }
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ } while (message); /* until no more messages */
+ if (fflush (sf)) {
+ sprintf (tmp,"Error finishing scratch file: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ }
+ i = ftell (sf); /* size of scratch file */
+ if (tstream != stream) tstream = mail_close (tstream);
+
+ MM_CRITICAL (stream); /* go critical */
+ /* try to open readwrite for UIDPLUS */
+ if ((tstream = mail_open_work (&mmdfdriver,NIL,mailbox,
+ OP_SILENT|OP_NOKOD)) && tstream->rdonly)
+ tstream = mail_close (tstream);
+ if (au && !tstream) { /* wanted an APPENDUID? */
+ sprintf (tmp,"Unable to re-open mailbox for APPENDUID: %.80s",mailbox);
+ MM_LOG (tmp,WARN);
+ au = NIL;
+ }
+ if (((fd = mmdf_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lock,LOCK_EX)) < 0) ||
+ !(df = fdopen (fd,"ab"))) {
+ MM_NOCRITICAL (stream); /* done with critical */
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ rewind (sf);
+ tp[1] = time (0); /* set mtime to now */
+ /* write all messages */
+ if (!mmdf_append_msgs (tstream,sf,df,au ? dst : NIL) ||
+ (fflush (df) == EOF) || fsync (fd)) {
+ sprintf (buf,"Message append failed: %s",strerror (errno));
+ MM_LOG (buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ tp[0] = /* preserve \Marked status */
+ ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
+ sbuf.st_atime : tp[1];
+ ret = NIL; /* return error */
+ }
+ else tp[0] = tp[1] - 1; /* set atime to now-1 if successful copy */
+ utime (file,tp); /* set the times */
+ fclose (sf); /* done with scratch file */
+ /* force UIDVALIDITY assignment now */
+ if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0);
+ /* return sets if doing APPENDUID */
+ if (au && ret) (*au) (mailbox,tstream->uid_validity,dst);
+ else mail_free_searchset (&dst);
+ mmdf_unlock (fd,NIL,&lock); /* unlock and close mailbox */
+ fclose (df);
+ if (tstream) { /* update last UID if we can */
+ MMDFLOCAL *local = (MMDFLOCAL *) tstream->local;
+ local->dirty = T; /* do a rewrite */
+ local->appending = T; /* but not at the cost of marking as old */
+ tstream = mail_close (tstream);
+ }
+ MM_NOCRITICAL (stream); /* release critical */
+ return ret;
+}
+
+/* Collect and write single message to append scratch file
+ * Accepts: MAIL stream
+ * scratch file
+ * flags
+ * date
+ * message stringstruct
+ * Returns: NIL if write error, else T
+ */
+
+int mmdf_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg)
+{
+ unsigned char *s,*t;
+ unsigned long uf;
+ long f = mail_parse_flags (stream,flags,&uf);
+ /* write metadata, note date ends with NL */
+ if (fprintf (sf,"%ld %lu %s",f,SIZE (msg) + 1,date) < 0) return NIL;
+ while (uf) /* write user flags */
+ if ((s = stream->user_flags[find_rightmost_bit (&uf)]) &&
+ (fprintf (sf," %s",s) < 0)) return NIL;
+ if (putc ('\n',sf) == EOF) return NIL;
+ while (SIZE (msg)) { /* copy text to scratch file */
+ for (s = (unsigned char *) msg->curpos, t = s + msg->cursize; s < t; ++s)
+ if (!*s) *s = 0x80; /* disallow NUL */
+ /* write buffered text */
+ if (fwrite (msg->curpos,1,msg->cursize,sf) == msg->cursize)
+ SETPOS (msg,GETPOS (msg) + msg->cursize);
+ else return NIL; /* failed */
+ }
+ /* write trailing newline and return */
+ return (putc ('\n',sf) == EOF) ? NIL : T;
+}
+
+/* Append messages from scratch file to mailbox
+ * Accepts: MAIL stream
+ * source file
+ * destination file
+ * uidset to update if non-NIL
+ * Returns: T if success, NIL if failure
+ */
+
+int mmdf_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set)
+{
+ int c;
+ long f;
+ unsigned long i,j;
+ char *x,tmp[MAILTMPLEN];
+ int hdrp = T;
+ /* get message metadata line */
+ while (fgets (tmp,MAILTMPLEN,sf)) {
+ if (!(isdigit (tmp[0]) && strchr (tmp,'\n'))) return NIL;
+ f = strtol (tmp,&x,10); /* get flags */
+ if (!((*x++ == ' ') && isdigit (*x))) return NIL;
+ i = strtoul (x,&x,10); /* get message size */
+ if ((*x++ != ' ') || /* build initial header */
+ (fprintf (df,"%sFrom %s@%s %sStatus: ",mmdfhdr,myusername(),
+ mylocalhost(),x) < 0) ||
+ (f&fSEEN && (putc ('R',df) == EOF)) ||
+ (fputs ("\nX-Status: ",df) == EOF) ||
+ (f&fDELETED && (putc ('D',df) == EOF)) ||
+ (f&fFLAGGED && (putc ('F',df) == EOF)) ||
+ (f&fANSWERED && (putc ('A',df) == EOF)) ||
+ (f&fDRAFT && (putc ('T',df) == EOF)) ||
+ (fputs ("\nX-Keywords:",df) == EOF)) return NIL;
+ /* copy keywords */
+ while ((c = getc (sf)) != '\n') switch (c) {
+ case EOF:
+ return NIL;
+ default:
+ if (putc (c,df) == EOF) return NIL;
+ }
+ if ((putc ('\n',df) == EOF) ||
+ (set && (fprintf (df,"X-UID: %lu\n",++(stream->uid_last)) < 0)))
+ return NIL;
+
+ for (c = '\n'; i && fgets (tmp,MAILTMPLEN,sf); c = tmp[j-1]) {
+ /* get read line length */
+ if (i < (j = strlen (tmp))) fatal ("mmdf_append_msgs overrun");
+ i -= j; /* number of bytes left */
+ /* squish out ^A and CRs (note copies NUL) */
+ for (x = tmp; x = strpbrk (x,"\01\r"); --j) memmove (x,x+1,j-(x-tmp));
+ if (!j) continue; /* do nothing if line emptied */
+ /* start of line? */
+ if ((c == '\n')) switch (tmp[0]) {
+ case 'S': case 's': /* possible "Status:" */
+ if (hdrp && (j > 6) && ((tmp[1] == 't') || (tmp[1] == 'T')) &&
+ ((tmp[2] == 'a') || (tmp[2] == 'A')) &&
+ ((tmp[3] == 't') || (tmp[3] == 'T')) &&
+ ((tmp[4] == 'u') || (tmp[4] == 'U')) &&
+ ((tmp[5] == 's') || (tmp[5] == 'S')) && (tmp[6] == ':') &&
+ (fputs ("X-Original-",df) == EOF)) return NIL;
+ break;
+ case 'X': case 'x': /* possible X-??? header */
+ if (hdrp && (tmp[1] == '-') &&
+ /* possible X-UID: */
+ (((j > 5) && ((tmp[2] == 'U') || (tmp[2] == 'u')) &&
+ ((tmp[3] == 'I') || (tmp[3] == 'i')) &&
+ ((tmp[4] == 'D') || (tmp[4] == 'd')) && (tmp[5] == ':')) ||
+ /* possible X-IMAP: */
+ ((j > 6) && ((tmp[2] == 'I') || (tmp[2] == 'i')) &&
+ ((tmp[3] == 'M') || (tmp[3] == 'm')) &&
+ ((tmp[4] == 'A') || (tmp[4] == 'a')) &&
+ ((tmp[5] == 'P') || (tmp[5] == 'p')) &&
+ ((tmp[6] == ':') ||
+ /* or X-IMAPbase: */
+ ((j > 10) && ((tmp[6] == 'b') || (tmp[6] == 'B')) &&
+ ((tmp[7] == 'a') || (tmp[7] == 'A')) &&
+ ((tmp[8] == 's') || (tmp[8] == 'S')) &&
+ ((tmp[9] == 'e') || (tmp[9] == 'E')) && (tmp[10] == ':')))) ||
+ /* possible X-Status: */
+ ((j > 8) && ((tmp[2] == 'S') || (tmp[2] == 's')) &&
+ ((tmp[3] == 't') || (tmp[3] == 'T')) &&
+ ((tmp[4] == 'a') || (tmp[4] == 'A')) &&
+ ((tmp[5] == 't') || (tmp[5] == 'T')) &&
+ ((tmp[6] == 'u') || (tmp[6] == 'U')) &&
+ ((tmp[7] == 's') || (tmp[7] == 'S')) && (tmp[8] == ':')) ||
+ /* possible X-Keywords: */
+ ((j > 10) && ((tmp[2] == 'K') || (tmp[2] == 'k')) &&
+ ((tmp[3] == 'e') || (tmp[3] == 'E')) &&
+ ((tmp[4] == 'y') || (tmp[4] == 'Y')) &&
+ ((tmp[5] == 'w') || (tmp[5] == 'W')) &&
+ ((tmp[6] == 'o') || (tmp[6] == 'O')) &&
+ ((tmp[7] == 'r') || (tmp[7] == 'R')) &&
+ ((tmp[8] == 'd') || (tmp[8] == 'D')) &&
+ ((tmp[9] == 's') || (tmp[9] == 'S')) && (tmp[10] == ':'))) &&
+ (fputs ("X-Original-",df) == EOF)) return NIL;
+ break;
+ case '\n': /* blank line */
+ hdrp = NIL;
+ break;
+ default: /* nothing to do */
+ break;
+ }
+ /* just write the line */
+ if (fwrite (tmp,1,j,df) != j) return NIL;
+ }
+ /* make sure read entire msg & wrote trailer */
+ if (i || (fputs (mmdfhdr,df) == EOF)) return NIL;
+ /* update set */
+ if (stream) mail_append_set (set,stream->uid_last);
+ }
+ return T;
+}
+
+/* Internal routines */
+
+
+/* MMDF mail abort stream
+ * Accepts: MAIL stream
+ */
+
+void mmdf_abort (MAILSTREAM *stream)
+{
+ if (LOCAL) { /* only if a file is open */
+ if (LOCAL->fd >= 0) close (LOCAL->fd);
+ if (LOCAL->ld >= 0) { /* have a mailbox lock? */
+ flock (LOCAL->ld,LOCK_UN);/* yes, release the lock */
+ close (LOCAL->ld); /* close the lock file */
+ unlink (LOCAL->lname); /* and delete it */
+ }
+ if (LOCAL->lname) fs_give ((void **) &LOCAL->lname);
+ /* free local text buffers */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data);
+ if (LOCAL->linebuf) fs_give ((void **) &LOCAL->linebuf);
+ if (LOCAL->line) fs_give ((void **) &LOCAL->line);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* MMDF open and lock mailbox
+ * Accepts: file name to open/lock
+ * file open mode
+ * destination buffer for lock file name
+ * type of locking operation (LOCK_SH or LOCK_EX)
+ */
+
+int mmdf_lock (char *file,int flags,int mode,DOTLOCK *lock,int op)
+{
+ int fd;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ (*bn) (BLOCK_FILELOCK,NIL);
+ /* try locking the easy way */
+ if (dotlock_lock (file,lock,-1)) {
+ /* got dotlock file, easy open */
+ if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
+ else dotlock_unlock (lock); /* open failed, free the dotlock */
+ }
+ /* no dot lock file, open file now */
+ else if ((fd = open (file,flags,mode)) >= 0) {
+ /* try paranoid way to make a dot lock file */
+ if (dotlock_lock (file,lock,fd)) {
+ close (fd); /* get fresh fd in case of timing race */
+ if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
+ /* open failed, free the dotlock */
+ else dotlock_unlock (lock);
+ }
+ else flock (fd,op); /* paranoid way failed, just flock() it */
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return fd;
+}
+
+/* MMDF unlock and close mailbox
+ * Accepts: file descriptor
+ * (optional) mailbox stream to check atime/mtime
+ * (optional) lock file name
+ */
+
+void mmdf_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock)
+{
+ if (stream) { /* need to muck with times? */
+ struct stat sbuf;
+ time_t tp[2];
+ time_t now = time (0);
+ fstat (fd,&sbuf); /* get file times */
+ if (LOCAL->ld >= 0) { /* yes, readwrite session? */
+ tp[0] = now; /* set atime to now */
+ /* set mtime to (now - 1) if necessary */
+ tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
+ }
+ else if (stream->recent) { /* readonly with recent messages */
+ if ((sbuf.st_atime >= sbuf.st_mtime) ||
+ (sbuf.st_atime >= sbuf.st_ctime))
+ /* keep past mtime, whack back atime */
+ tp[0] = (tp[1] = (sbuf.st_mtime < now) ? sbuf.st_mtime : now) - 1;
+ else now = 0; /* no time change needed */
+ }
+ /* readonly with no recent messages */
+ else if ((sbuf.st_atime < sbuf.st_mtime) ||
+ (sbuf.st_atime < sbuf.st_ctime)) {
+ tp[0] = now; /* set atime to now */
+ /* set mtime to (now - 1) if necessary */
+ tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
+ }
+ else now = 0; /* no time change needed */
+ /* set the times, note change */
+ if (now && !utime (stream->mailbox,tp)) LOCAL->filetime = tp[1];
+ }
+ flock (fd,LOCK_UN); /* release flock'ers */
+ if (!stream) close (fd); /* close the file if no stream */
+ dotlock_unlock (lock); /* flush the lock file if any */
+}
+
+/* MMDF mail parse and lock mailbox
+ * Accepts: MAIL stream
+ * space to write lock file name
+ * type of locking operation
+ * Returns: T if parse OK, critical & mailbox is locked shared; NIL if failure
+ */
+
+int mmdf_parse (MAILSTREAM *stream,DOTLOCK *lock,int op)
+{
+ int ti,zn,m;
+ unsigned long i,j,k;
+ unsigned char c,*s,*t,*u,tmp[MAILTMPLEN],date[30];
+ int retain = T;
+ unsigned long nmsgs = stream->nmsgs;
+ unsigned long prevuid = nmsgs ? mail_elt (stream,nmsgs)->private.uid : 0;
+ unsigned long recent = stream->recent;
+ unsigned long oldnmsgs = stream->nmsgs;
+ short silent = stream->silent;
+ short pseudoseen = NIL;
+ struct stat sbuf;
+ STRING bs;
+ FDDATA d;
+ MESSAGECACHE *elt;
+ mail_lock (stream); /* guard against recursion or pingers */
+ /* toss out previous descriptor */
+ if (LOCAL->fd >= 0) close (LOCAL->fd);
+ MM_CRITICAL (stream); /* open and lock mailbox (shared OK) */
+ if ((LOCAL->fd = mmdf_lock (stream->mailbox,(LOCAL->ld >= 0) ?
+ O_RDWR : O_RDONLY,
+ (long)mail_parameters(NIL,GET_MBXPROTECTION,NIL),
+ lock,op)) < 0) {
+ sprintf (tmp,"Mailbox open failed, aborted: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ mmdf_abort (stream);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream); /* done with critical */
+ return NIL;
+ }
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* validate change in size */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (tmp,"Mailbox shrank from %lu to %lu bytes, aborted",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR); /* this is pretty bad */
+ mmdf_unlock (LOCAL->fd,stream,lock);
+ mmdf_abort (stream);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream); /* done with critical */
+ return NIL;
+ }
+
+ /* new data? */
+ else if (i = sbuf.st_size - LOCAL->filesize) {
+ d.fd = LOCAL->fd; /* yes, set up file descriptor */
+ d.pos = LOCAL->filesize; /* get to that position in the file */
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* file chunk size */
+ INIT (&bs,fd_string,&d,i); /* initialize stringstruct */
+ /* skip leading whitespace for broken MTAs */
+ while (((c = CHR (&bs)) == '\n') || (c == '\r') ||
+ (c == ' ') || (c == '\t')) SNX (&bs);
+ if (SIZE (&bs)) { /* read new data */
+ /* remember internal header position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ s = mmdf_mbxline (stream,&bs,&i);
+ stream->silent = T; /* quell main program new message events */
+ do { /* read MMDF header */
+ if (!(i && ISMMDF (s))){/* see if valid MMDF header */
+ sprintf (tmp,"Unexpected changes to mailbox (try restarting): %.20s",
+ (char *) s);
+ /* see if we can back up to a line */
+ if (i && (j > MMDFHDRLEN)) {
+ SETPOS (&bs,j -= MMDFHDRLEN);
+ /* read previous line */
+ s = mmdf_mbxline (stream,&bs,&i);
+ /* kill the error if it looks good */
+ if (i && ISMMDF (s)) tmp[0] = '\0';
+ }
+ if (tmp[0]) {
+ MM_LOG (tmp,ERROR);
+ mmdf_unlock (LOCAL->fd,stream,lock);
+ mmdf_abort (stream);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream);
+ return NIL;
+ }
+ }
+ /* instantiate first new message */
+ mail_exists (stream,++nmsgs);
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ recent++; /* assume recent by default */
+ elt->recent = T;
+ /* note position/size of internal header */
+ elt->private.special.offset = j;
+ elt->private.special.text.size = i;
+
+ s = mmdf_mbxline (stream,&bs,&i);
+ ti = 0; /* assume not a valid date */
+ zn = 0,t = NIL;
+ if (i) VALID (s,t,ti,zn);
+ if (ti) { /* generate plausible IMAPish date string */
+ /* this is also part of header */
+ elt->private.special.text.size += i;
+ date[2] = date[6] = date[20] = '-'; date[11] = ' ';
+ date[14] = date[17] = ':';
+ /* dd */
+ date[0] = t[ti - 2]; date[1] = t[ti - 1];
+ /* mmm */
+ date[3] = t[ti - 6]; date[4] = t[ti - 5]; date[5] = t[ti - 4];
+ /* hh */
+ date[12] = t[ti + 1]; date[13] = t[ti + 2];
+ /* mm */
+ date[15] = t[ti + 4]; date[16] = t[ti + 5];
+ if (t[ti += 6]==':'){ /* ss */
+ date[18] = t[++ti]; date[19] = t[++ti];
+ ti++; /* move to space */
+ }
+ else date[18] = date[19] = '0';
+ /* yy -- advance over timezone if necessary */
+ if (zn == ti) ti += (((t[zn+1] == '+') || (t[zn+1] == '-')) ? 6 : 4);
+ date[7] = t[ti + 1]; date[8] = t[ti + 2];
+ date[9] = t[ti + 3]; date[10] = t[ti + 4];
+ /* zzz */
+ t = zn ? (t + zn + 1) : (unsigned char *) "LCL";
+ date[21] = *t++; date[22] = *t++; date[23] = *t++;
+ if ((date[21] != '+') && (date[21] != '-')) date[24] = '\0';
+ else { /* numeric time zone */
+ date[24] = *t++; date[25] = *t++;
+ date[26] = '\0'; date[20] = ' ';
+ }
+ /* set internal date */
+ if (!mail_parse_date (elt,date)) {
+ sprintf (tmp,"Unable to parse internal date: %s",(char *) date);
+ MM_LOG (tmp,WARN);
+ }
+ }
+ else { /* make date from file date */
+ struct tm *tm = gmtime (&sbuf.st_mtime);
+ elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
+ elt->year = tm->tm_year + 1900 - BASEYEAR;
+ elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
+ elt->seconds = tm->tm_sec;
+ elt->zhours = 0; elt->zminutes = 0;
+ t = NIL; /* suppress line read */
+ }
+ /* header starts here */
+ elt->private.msg.header.offset = elt->private.special.text.size;
+
+ do { /* look for message body */
+ j = GETPOS (&bs); /* note position before line */
+ if (t) s = t = mmdf_mbxline (stream,&bs,&i);
+ else t = s; /* this line read was suppressed */
+ if (ISMMDF (s)) { /* found terminator in header? */
+ SETPOS (&bs,j); /* oops, back up before line */
+ /* must insert a newline */
+ elt->private.spare.data++;
+ break; /* punt */
+ }
+ /* this line is part of header */
+ elt->private.msg.header.text.size += i;
+ if (i) switch (*s) { /* check header lines */
+ case 'X': /* possible X-???: line */
+ if (s[1] == '-') { /* must be immediately followed by hyphen */
+ /* X-Status: becomes Status: in S case */
+ if (s[2] == 'S' && s[3] == 't' && s[4] == 'a' && s[5] == 't' &&
+ s[6] == 'u' && s[7] == 's' && s[8] == ':') s += 2;
+ /* possible X-Keywords */
+ else if (s[2] == 'K' && s[3] == 'e' && s[4] == 'y' &&
+ s[5] == 'w' && s[6] == 'o' && s[7] == 'r' &&
+ s[8] == 'd' && s[9] == 's' && s[10] == ':') {
+ SIZEDTEXT uf;
+ retain = NIL; /* don't retain continuation */
+ s += 11; /* flush leading whitespace */
+ while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n'))){
+ while (*s == ' ') s++;
+ /* find end of keyword */
+ if (!(u = strpbrk (s," \n\r"))) u = s + strlen (s);
+ /* got a keyword? */
+ if ((k = (u - s)) && (k <= MAXUSERFLAG)) {
+ uf.data = (unsigned char *) s;
+ uf.size = k;
+ for (j = 0; (j < NUSERFLAGS) && stream->user_flags[j]; ++j)
+ if (!compare_csizedtext (stream->user_flags[j],&uf)) {
+ elt->user_flags |= ((long) 1) << j;
+ break;
+ }
+ }
+ s = u; /* advance to next keyword */
+ }
+ break;
+ }
+
+ /* possible X-IMAP */
+ else if ((s[2] == 'I') && (s[3] == 'M') && (s[4] == 'A') &&
+ (s[5] == 'P') && ((m = (s[6] == ':')) ||
+ ((s[6] == 'b') && (s[7] == 'a') &&
+ (s[8] == 's') && (s[9] == 'e') &&
+ (s[10] == ':')))) {
+ retain = NIL; /* don't retain continuation */
+ if ((nmsgs == 1) && !stream->uid_validity) {
+ /* advance to data */
+ s += m ? 7 : 11;
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ j = 0; /* slurp UID validity */
+ /* found a digit? */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ /* must have valid UID validity and UID last */
+ if (j && isdigit (*s)) {
+ /* pseudo-header seen if X-IMAP */
+ if (m) pseudoseen = LOCAL->pseudo = T;
+ /* save UID validity */
+ stream->uid_validity = j;
+ j = 0; /* slurp UID last */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* save UID last */
+ stream->uid_last = j;
+ /* process keywords */
+ for (j = 0; (*s != '\n') && ((*s != '\r')||(s[1] != '\n'));
+ s = u,j++) {
+ /* flush leading whitespace */
+ while (*s == ' ') s++;
+ u = strpbrk (s," \n\r");
+ /* got a keyword? */
+ if ((j < NUSERFLAGS) && (k = (u - s)) &&
+ (k <= MAXUSERFLAG)) {
+ if (stream->user_flags[j])
+ fs_give ((void **) &stream->user_flags[j]);
+ stream->user_flags[j] = (char *) fs_get (k + 1);
+ strncpy (stream->user_flags[j],s,k);
+ stream->user_flags[j][k] = '\0';
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ /* possible X-UID */
+ else if (s[2] == 'U' && s[3] == 'I' && s[4] == 'D' &&
+ s[5] == ':') {
+ retain = NIL; /* don't retain continuation */
+ /* only believe if have a UID validity */
+ if (stream->uid_validity && ((nmsgs > 1) || !pseudoseen)) {
+ s += 6; /* advance to UID value */
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ j = 0;
+ /* found a digit? */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* flush remainder of line */
+ while (*s != '\n') s++;
+ /* make sure not duplicated */
+ if (elt->private.uid)
+ sprintf (tmp,"Message %lu UID %lu already has UID %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,elt->private.uid);
+ /* make sure UID doesn't go backwards */
+ else if (j <= prevuid)
+ sprintf (tmp,"Message %lu UID %lu less than %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,prevuid + 1);
+#if 0 /* this is currently broken by UIDPLUS */
+ /* or skip by mailbox's recorded last */
+ else if (j > stream->uid_last)
+ sprintf (tmp,"Message %lu UID %lu greater than last %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,stream->uid_last);
+#endif
+ else { /* normal UID case */
+ prevuid = elt->private.uid = j;
+#if 1 /* temporary kludge for UIDPLUS */
+ if (prevuid > stream->uid_last) {
+ stream->uid_last = prevuid;
+ LOCAL->ddirty = LOCAL->dirty = T;
+ }
+#endif
+ break; /* exit this cruft */
+ }
+ MM_LOG (tmp,WARN);
+ /* invalidate UID validity */
+ stream->uid_validity = 0;
+ elt->private.uid = 0;
+ }
+ break;
+ }
+ }
+ /* otherwise fall into S case */
+
+ case 'S': /* possible Status: line */
+ if (s[0] == 'S' && s[1] == 't' && s[2] == 'a' && s[3] == 't' &&
+ s[4] == 'u' && s[5] == 's' && s[6] == ':') {
+ retain = NIL; /* don't retain continuation */
+ s += 6; /* advance to status flags */
+ do switch (*s++) {/* parse flags */
+ case 'R': /* message read */
+ elt->seen = T;
+ break;
+ case 'O': /* message old */
+ if (elt->recent) {
+ elt->recent = NIL;
+ recent--; /* it really wasn't recent */
+ }
+ break;
+ case 'D': /* message deleted */
+ elt->deleted = T;
+ break;
+ case 'F': /* message flagged */
+ elt->flagged = T;
+ break;
+ case 'A': /* message answered */
+ elt->answered = T;
+ break;
+ case 'T': /* message is a draft */
+ elt->draft = T;
+ break;
+ default: /* some other crap */
+ break;
+ } while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n')));
+ break; /* all done */
+ }
+ /* otherwise fall into default case */
+
+ default: /* ordinary header line */
+ if ((*s == 'S') || (*s == 's') ||
+ (((*s == 'X') || (*s == 'x')) && (s[1] == '-'))) {
+ unsigned char *e,*v;
+ /* must match what mail_filter() does */
+ for (u = s,v = tmp,e = u + min (i,MAILTMPLEN - 1);
+ (u < e) && ((c = (*u ? *u : (*u = ' '))) != ':') &&
+ ((c > ' ') || ((c != ' ') && (c != '\t') &&
+ (c != '\r') && (c != '\n')));
+ *v++ = *u++);
+ *v = '\0'; /* tie off */
+ /* matches internal header? */
+ if (!compare_cstring (tmp,"STATUS") ||
+ !compare_cstring (tmp,"X-STATUS") ||
+ !compare_cstring (tmp,"X-KEYWORDS") ||
+ !compare_cstring (tmp,"X-UID") ||
+ !compare_cstring (tmp,"X-IMAP") ||
+ !compare_cstring (tmp,"X-IMAPBASE")) {
+ char err[MAILTMPLEN];
+ sprintf (err,"Discarding bogus %s header in message %lu",
+ (char *) tmp,elt->msgno);
+ MM_LOG (err,WARN);
+ retain = NIL; /* don't retain continuation */
+ break; /* different case or something */
+ }
+ }
+ /* retain or non-continuation? */
+ if (retain || ((*s != ' ') && (*s != '\t'))) {
+ retain = T; /* retaining continuation now */
+ /* line length in LF format newline */
+ for (j = k = 0; j < i; ++j) if (s[j] != '\r') ++k;
+ /* "internal" header size */
+ elt->private.spare.data += k;
+ /* message size */
+ elt->rfc822_size += k + 1;
+ }
+ else {
+ char err[MAILTMPLEN];
+ sprintf (err,"Discarding bogus continuation in msg %lu: %.80s",
+ elt->msgno,(char *) s);
+ if (u = strpbrk (err,"\r\n")) *u = '\0';
+ MM_LOG (err,WARN);
+ break; /* different case or something */
+ }
+ break;
+ }
+ } while (i && (*t != '\n') && ((*t != '\r') || (t[1] != '\n')));
+ /* "internal" header sans trailing newline */
+ if (i) elt->private.spare.data--;
+ /* assign a UID if none found */
+ if (((nmsgs > 1) || !pseudoseen) && !elt->private.uid) {
+ prevuid = elt->private.uid = ++stream->uid_last;
+ elt->private.dirty = T;
+ }
+ else elt->private.dirty = elt->recent;
+
+ /* note size of header, location of text */
+ elt->private.msg.header.text.size =
+ (elt->private.msg.text.offset =
+ (LOCAL->filesize + GETPOS (&bs)) - elt->private.special.offset) -
+ elt->private.special.text.size;
+ /* note current position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ if (i) do { /* look for next message */
+ s = mmdf_mbxline (stream,&bs,&i);
+ if (i) { /* got new data? */
+ if (ISMMDF (s)) break;
+ else { /* not a header line, add it to message */
+ elt->rfc822_size += i;
+ for (j = 0; j < i; ++j) switch (s[j]) {
+ case '\r': /* squeeze out CRs */
+ elt->rfc822_size -= 1;
+ break;
+ case '\n': /* LF becomes CRLF */
+ elt->rfc822_size += 1;
+ break;
+ default:
+ break;
+ }
+ /* update current position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ }
+ }
+ } while (i); /* until found a header */
+ elt->private.msg.text.text.size = j -
+ (elt->private.special.offset + elt->private.msg.text.offset);
+ if (i) { /* get next header line */
+ /* remember first internal header position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ s = mmdf_mbxline (stream,&bs,&i);
+ }
+ /* until end of buffer */
+ } while (!stream->sniff && i);
+ if (pseudoseen) { /* flush pseudo-message if present */
+ /* decrement recent count */
+ if (mail_elt (stream,1)->recent) recent--;
+ /* and the exists count */
+ mail_exists (stream,nmsgs--);
+ mail_expunged(stream,1);/* fake an expunge of that message */
+ }
+ /* need to start a new UID validity? */
+ if (!stream->uid_validity) {
+ stream->uid_validity = time (0);
+ /* in case a whiner with no life */
+ if (mail_parameters (NIL,GET_USERHASNOLIFE,NIL))
+ stream->uid_nosticky = T;
+ else if (nmsgs) { /* don't bother if empty file */
+ /* make dirty to restart UID epoch */
+ LOCAL->ddirty = LOCAL->dirty = T;
+ /* need to rewrite msg 1 if not pseudo */
+ if (!LOCAL->pseudo) mail_elt (stream,1)->private.dirty = T;
+ MM_LOG ("Assigning new unique identifiers to all messages",NIL);
+ }
+ }
+ stream->nmsgs = oldnmsgs; /* whack it back down */
+ stream->silent = silent; /* restore old silent setting */
+ /* notify upper level of new mailbox sizes */
+ mail_exists (stream,nmsgs);
+ mail_recent (stream,recent);
+ /* mark dirty so O flags are set */
+ if (recent) LOCAL->dirty = T;
+ }
+ }
+ /* no change, don't babble if never got time */
+ else if (LOCAL->filetime && LOCAL->filetime != sbuf.st_mtime)
+ MM_LOG ("New mailbox modification time but apparently no changes",WARN);
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ LOCAL->filetime = sbuf.st_mtime;
+ return T; /* return the winnage */
+}
+
+/* MMDF read line from mailbox
+ * Accepts: mail stream
+ * stringstruct
+ * pointer to line size
+ * Returns: pointer to input line
+ */
+
+char *mmdf_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size)
+{
+ unsigned long i,j,k,m;
+ char *s,*t,*te;
+ char *ret = "";
+ /* flush old buffer */
+ if (LOCAL->line) fs_give ((void **) &LOCAL->line);
+ /* if buffer needs refreshing */
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ if (SIZE (bs)) { /* find newline */
+ /* end of fast scan */
+ te = (t = (s = bs->curpos) + bs->cursize) - 12;
+ while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
+ --s; /* back up */
+ break; /* exit loop */
+ }
+ /* final character-at-a-time scan */
+ while ((s < t) && (*s != '\n')) ++s;
+ /* difficult case if line spans buffer */
+ if ((i = s - bs->curpos) == bs->cursize) {
+ /* have space in line buffer? */
+ if (i > LOCAL->linebuflen) {
+ fs_give ((void **) &LOCAL->linebuf);
+ LOCAL->linebuf = (char *) fs_get (LOCAL->linebuflen = i);
+ }
+ /* remember what we have so far */
+ memcpy (LOCAL->linebuf,bs->curpos,i);
+ /* load next buffer */
+ SETPOS (bs,k = GETPOS (bs) + i);
+ /* end of fast scan */
+ te = (t = (s = bs->curpos) + bs->cursize) - 12;
+ /* fast scan in overlap buffer */
+ while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
+ --s; /* back up */
+ break; /* exit loop */
+ }
+
+ /* final character-at-a-time scan */
+ while ((s < t) && (*s != '\n')) ++s;
+ /* huge line? */
+ if ((j = s - bs->curpos) == bs->cursize) {
+ SETPOS (bs,GETPOS (bs) + j);
+ /* look for end of line (s-l-o-w!!) */
+ for (m = SIZE (bs); m && (SNX (bs) != '\n'); --m,++j);
+ SETPOS (bs,k); /* go back to where it started */
+ }
+ /* got size of data, make buffer for return */
+ ret = LOCAL->line = (char *) fs_get (i + j + 2);
+ /* copy first chunk */
+ memcpy (ret,LOCAL->linebuf,i);
+ while (j) { /* copy remainder */
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ memcpy (ret + i,bs->curpos,k = min (j,bs->cursize));
+ i += k; /* account for this much read in */
+ j -= k;
+ bs->curpos += k; /* increment new position */
+ bs->cursize -= k; /* eat that many bytes */
+ }
+ /* read newline at end */
+ if (SIZE (bs)) ret[i++] = SNX (bs);
+ ret[i] = '\0'; /* makes debugging easier */
+ }
+ else { /* this is easy */
+ ret = bs->curpos; /* string it at this position */
+ bs->curpos += ++i; /* increment new position */
+ bs->cursize -= i; /* eat that many bytes */
+ }
+ *size = i; /* return that to user */
+ }
+ else *size = 0; /* end of data, return empty */
+ /* embedded MMDF header at end of line? */
+ if ((*size > sizeof (MMDFHDRTXT)) &&
+ (s = ret + *size - (i = sizeof (MMDFHDRTXT) - 1)) && ISMMDF (s)) {
+ SETPOS (bs,GETPOS (bs) - i);/* back up to start of MMDF header */
+ *size -= i; /* reduce length of line */
+ ret[*size - 1] = '\n'; /* force newline at end */
+ }
+ return ret;
+}
+
+/* MMDF make pseudo-header
+ * Accepts: MAIL stream
+ * buffer to write pseudo-header
+ * Returns: length of pseudo-header
+ */
+
+unsigned long mmdf_pseudo (MAILSTREAM *stream,char *hdr)
+{
+ int i;
+ char *s,tmp[MAILTMPLEN];
+ time_t now = time (0);
+ rfc822_fixed_date (tmp);
+ sprintf (hdr,"%sFrom %s %.24s\nDate: %s\nFrom: %s <%s@%.80s>\nSubject: %s\nMessage-ID: <%lu@%.80s>\nX-IMAP: %010lu %010lu",
+ mmdfhdr,pseudo_from,ctime (&now),
+ tmp,pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
+ (unsigned long) now,mylocalhost (),stream->uid_validity,
+ stream->uid_last);
+ for (s = hdr + strlen (hdr),i = 0; i < NUSERFLAGS; ++i)
+ if (stream->user_flags[i])
+ sprintf (s += strlen (s)," %s",stream->user_flags[i]);
+ sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n%s",pseudo_msg,mmdfhdr);
+ return strlen (hdr);
+}
+
+/* MMDF make status string
+ * Accepts: MAIL stream
+ * destination string to write
+ * message cache entry
+ * UID to write if non-zero (else use elt->private.uid)
+ * non-zero flag to write UID (.LT. 0 to write UID base info too)
+ * Returns: length of string
+ */
+
+unsigned long mmdf_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
+ unsigned long uid,long flag)
+{
+ char *t,stack[64];
+ char *s = status;
+ unsigned long n;
+ int pad = 50;
+ int sticky = uid ? T : !stream->uid_nosticky;
+ /* This used to use sprintf(), but thanks to certain cretinous C libraries
+ with horribly slow implementations of sprintf() I had to change it to this
+ mess. At least it should be fast. */
+ if ((flag < 0) && sticky) { /* need to write X-IMAPbase: header? */
+ *s++ = 'X'; *s++ = '-'; *s++ = 'I'; *s++ = 'M'; *s++ = 'A'; *s++ = 'P';
+ *s++ = 'b'; *s++ = 'a'; *s++ = 's'; *s++ = 'e'; *s++ = ':'; *s++ = ' ';
+ t = stack;
+ n = stream->uid_validity; /* push UID validity digits on the stack */
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ /* pop UID validity digits from stack */
+ while (t > stack) *s++ = *--t;
+ *s++ = ' ';
+ n = stream->uid_last; /* push UID last digits on the stack */
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ /* pop UID last digits from stack */
+ while (t > stack) *s++ = *--t;
+ for (n = 0; n < NUSERFLAGS; ++n) if (t = stream->user_flags[n])
+ for (*s++ = ' '; *t; *s++ = *t++);
+ *s++ = '\n';
+ pad += 30; /* increased padding if have IMAPbase */
+ }
+ *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't'; *s++ = 'u'; *s++ = 's';
+ *s++ = ':'; *s++ = ' ';
+ if (elt->seen) *s++ = 'R';
+ /* only write O if have a UID */
+ if (flag && (!elt->recent || !LOCAL->appending)) *s++ = 'O';
+ *s++ = '\n';
+ *s++ = 'X'; *s++ = '-'; *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't';
+ *s++ = 'u'; *s++ = 's'; *s++ = ':'; *s++ = ' ';
+ if (elt->deleted) *s++ = 'D';
+ if (elt->flagged) *s++ = 'F';
+ if (elt->answered) *s++ = 'A';
+ if (elt->draft) *s++ = 'T';
+ *s++ = '\n';
+
+ if (sticky) { /* only do this if UIDs sticky */
+ *s++ = 'X'; *s++ = '-'; *s++ = 'K'; *s++ = 'e'; *s++ = 'y'; *s++ = 'w';
+ *s++ = 'o'; *s++ = 'r'; *s++ = 'd'; *s++ = 's'; *s++ = ':';
+ if (n = elt->user_flags) do {
+ *s++ = ' ';
+ for (t = stream->user_flags[find_rightmost_bit (&n)]; *t; *s++ = *t++);
+ } while (n);
+ n = s - status; /* get size of stuff so far */
+ /* pad X-Keywords to make size constant */
+ if (n < pad) for (n = pad - n; n > 0; --n) *s++ = ' ';
+ *s++ = '\n';
+ if (flag) { /* want to include UID? */
+ t = stack;
+ /* push UID digits on the stack */
+ n = uid ? uid : elt->private.uid;
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ *s++ = 'X'; *s++ = '-'; *s++ = 'U'; *s++ = 'I'; *s++ = 'D'; *s++ = ':';
+ *s++ = ' ';
+ /* pop UID from stack */
+ while (t > stack) *s++ = *--t;
+ *s++ = '\n';
+ }
+ }
+ *s++ = '\n'; *s = '\0'; /* end of extended message status */
+ return s - status; /* return size of resulting string */
+}
+
+/* Rewrite mailbox file
+ * Accepts: MAIL stream, must be critical and locked
+ * return pointer to number of expunged messages if want expunge
+ * lock file name
+ * expunge sequence, not deleted flag
+ * Returns: T if success and mailbox unlocked, NIL if failure
+ */
+
+#define OVERFLOWBUFLEN 8192 /* initial overflow buffer length */
+
+long mmdf_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock,
+ long flags)
+{
+ MESSAGECACHE *elt;
+ MMDFFILE f;
+ char *s;
+ time_t tp[2];
+ long ret,flag;
+ unsigned long i,j;
+ unsigned long recent = stream->recent;
+ unsigned long size = LOCAL->pseudo ? mmdf_pseudo (stream,LOCAL->buf) : 0;
+ if (nexp) *nexp = 0; /* initially nothing expunged */
+ /* calculate size of mailbox after rewrite */
+ for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs; i++) {
+ elt = mail_elt (stream,i); /* get cache */
+ if (!(nexp && elt->deleted && (flags ? elt->sequence : T))) {
+ /* add RFC822 size of this message */
+ size += elt->private.special.text.size + elt->private.spare.data +
+ mmdf_xstatus (stream,LOCAL->buf,elt,NIL,flag) +
+ elt->private.msg.text.text.size + MMDFHDRLEN;
+ flag = 1; /* only count X-IMAPbase once */
+ }
+ }
+ /* no messages, has a life, and no pseudo */
+ if (!size && !mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) {
+ LOCAL->pseudo = T; /* so make a pseudo-message now */
+ size = mmdf_pseudo (stream,LOCAL->buf);
+ }
+ /* extend the file as necessary */
+ if (ret = mmdf_extend (stream,size)) {
+ /* Set up buffered I/O file structure
+ * curpos current position being written through buffering
+ * filepos current position being written physically to the disk
+ * bufpos current position being written in the buffer
+ * protect current maximum position that can be written to the disk
+ * before buffering is forced
+ * The code tries to buffer so that that disk is written in multiples of
+ * OVERBLOWBUFLEN bytes.
+ */
+ f.stream = stream; /* note mail stream */
+ f.curpos = f.filepos = 0; /* start of file */
+ f.protect = stream->nmsgs ? /* initial protection pointer */
+ mail_elt (stream,1)->private.special.offset : 8192;
+ f.bufpos = f.buf = (char *) fs_get (f.buflen = OVERFLOWBUFLEN);
+
+ if (LOCAL->pseudo) /* update pseudo-header */
+ mmdf_write (&f,LOCAL->buf,mmdf_pseudo (stream,LOCAL->buf));
+ /* loop through all messages */
+ for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs;) {
+ elt = mail_elt (stream,i);/* get cache */
+ /* expunge this message? */
+ if (nexp && elt->deleted && (flags ? elt->sequence : T)) {
+ /* one less recent message */
+ if (elt->recent) --recent;
+ mail_expunged(stream,i);/* notify upper levels */
+ ++*nexp; /* count up one more expunged message */
+ }
+ else { /* preserve this message */
+ i++; /* advance to next message */
+ if ((flag < 0) || /* need to rewrite message? */
+ elt->private.dirty || (f.curpos != elt->private.special.offset) ||
+ (elt->private.msg.header.text.size !=
+ (elt->private.spare.data +
+ mmdf_xstatus (stream,LOCAL->buf,elt,NIL,flag)))) {
+ unsigned long newoffset = f.curpos;
+ /* yes, seek to internal header */
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
+ /* see if need to squeeze out a CR */
+ if (LOCAL->buf[elt->private.special.text.size - 2] == '\r') {
+ LOCAL->buf[--elt->private.special.text.size - 1] = '\n';
+ --size; /* squeezed out a CR from PC */
+ }
+ /* protection pointer moves to RFC822 header */
+ f.protect = elt->private.special.offset +
+ elt->private.msg.header.offset;
+ /* write internal header */
+ mmdf_write (&f,LOCAL->buf,elt->private.special.text.size);
+ /* get RFC822 header */
+ s = mmdf_header (stream,elt->msgno,&j,FT_INTERNAL);
+ /* in case this got decremented */
+ elt->private.msg.header.offset = elt->private.special.text.size;
+ /* header size, sans trailing newline */
+ if ((j < 2) || (s[j - 2] == '\n')) j--;
+ /* this can happen if CRs were squeezed */
+ if (j < elt->private.spare.data) {
+ /* so fix up counts */
+ size -= elt->private.spare.data - j;
+ elt->private.spare.data = j;
+ }
+ else if (j != elt->private.spare.data)
+ fatal ("header size inconsistent");
+ /* protection pointer moves to RFC822 text */
+ f.protect = elt->private.special.offset +
+ elt->private.msg.text.offset;
+ mmdf_write (&f,s,j); /* write RFC822 header */
+ /* write status and UID */
+ mmdf_write (&f,LOCAL->buf,
+ j = mmdf_xstatus (stream,LOCAL->buf,elt,NIL,flag));
+ flag = 1; /* only write X-IMAPbase once */
+ /* new file header size */
+ elt->private.msg.header.text.size = elt->private.spare.data + j;
+
+ /* did text move? */
+ if (f.curpos != f.protect) {
+ /* get message text */
+ s = mmdf_text_work (stream,elt,&j,FT_INTERNAL);
+ /* this can happen if CRs were squeezed */
+ if (j < elt->private.msg.text.text.size) {
+ /* so fix up counts */
+ size -= elt->private.msg.text.text.size - j;
+ elt->private.msg.text.text.size = j;
+ }
+ /* can't happen it says here */
+ else if (j > elt->private.msg.text.text.size)
+ fatal ("text size inconsistent");
+ /* new text offset, status/UID may change it */
+ elt->private.msg.text.offset = f.curpos - newoffset;
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset :
+ (f.curpos + j + MMDFHDRLEN);
+ mmdf_write (&f,s,j);/* write text */
+ /* write trailing newline */
+ mmdf_write (&f,mmdfhdr,MMDFHDRLEN);
+ }
+ else { /* tie off header and status */
+ mmdf_write (&f,NIL,NIL);
+ f.curpos = f.protect =/* restart everything at end of message */
+ f.filepos += elt->private.msg.text.text.size + MMDFHDRLEN;
+ }
+ /* new internal header offset */
+ elt->private.special.offset = newoffset;
+ elt->private.dirty =NIL;/* message is now clean */
+ }
+ else { /* no need to rewrite this message */
+ /* tie off previous message if needed */
+ mmdf_write (&f,NIL,NIL);
+ f.curpos = f.protect =/* restart everything at end of message */
+ f.filepos += elt->private.special.text.size +
+ elt->private.msg.header.text.size +
+ elt->private.msg.text.text.size + MMDFHDRLEN;
+ }
+ }
+ }
+
+ mmdf_write (&f,NIL,NIL); /* tie off final message */
+ if (size != f.filepos) fatal ("file size inconsistent");
+ fs_give ((void **) &f.buf); /* free buffer */
+ /* make sure tied off */
+ ftruncate (LOCAL->fd,LOCAL->filesize = size);
+ fsync (LOCAL->fd); /* make sure the updates take */
+ if (size && (flag < 0)) fatal ("lost UID base information");
+ /* no longer dirty */
+ LOCAL->ddirty = LOCAL->dirty = NIL;
+ /* notify upper level of new mailbox sizes */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ /* set atime to now, mtime a second earlier */
+ tp[1] = (tp[0] = time (0)) - 1;
+ /* set the times, note change */
+ if (!utime (stream->mailbox,tp)) LOCAL->filetime = tp[1];
+ close (LOCAL->fd); /* close and reopen file */
+ if ((LOCAL->fd = open (stream->mailbox,O_RDWR,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL)))
+ < 0) {
+ sprintf (LOCAL->buf,"Mailbox open failed, aborted: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ mmdf_abort (stream);
+ }
+ dotlock_unlock (lock); /* flush the lock file */
+ }
+ return ret; /* return state from algorithm */
+}
+
+/* Extend MMDF mailbox file
+ * Accepts: MAIL stream
+ * new desired size
+ * Return: T if success, else NIL
+ */
+
+long mmdf_extend (MAILSTREAM *stream,unsigned long size)
+{
+ unsigned long i = (size > LOCAL->filesize) ? size - LOCAL->filesize : 0;
+ if (i) { /* does the mailbox need to grow? */
+ if (i > LOCAL->buflen) { /* make sure have enough space */
+ /* this user won the lottery all right */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1);
+ }
+ memset (LOCAL->buf,'\0',i); /* get a block of nulls */
+ while (T) { /* until write successful or punt */
+ lseek (LOCAL->fd,LOCAL->filesize,L_SET);
+ if ((write (LOCAL->fd,LOCAL->buf,i) >= 0) && !fsync (LOCAL->fd)) break;
+ else {
+ long e = errno; /* note error before doing ftruncate */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ if (MM_DISKERROR (stream,e,NIL)) {
+ fsync (LOCAL->fd); /* user chose to punt */
+ sprintf (LOCAL->buf,"Unable to extend mailbox: %s",strerror (e));
+ if (!stream->silent) MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ }
+ }
+ }
+ return LONGT;
+}
+
+/* Write data to buffered file
+ * Accepts: buffered file pointer
+ * file data or NIL to indicate "flush buffer"
+ * date size (ignored for "flush buffer")
+ * Does not return until success
+ */
+
+void mmdf_write (MMDFFILE *f,char *buf,unsigned long size)
+{
+ unsigned long i,j,k;
+ if (buf) { /* doing buffered write? */
+ i = f->bufpos - f->buf; /* yes, get size of current buffer data */
+ /* yes, have space in current buffer chunk? */
+ if (j = i ? ((f->buflen - i) % OVERFLOWBUFLEN) : f->buflen) {
+ /* yes, fill up buffer as much as we can */
+ memcpy (f->bufpos,buf,k = min (j,size));
+ f->bufpos += k; /* new buffer position */
+ f->curpos += k; /* new current position */
+ if (j -= k) return; /* all done if still have buffer free space */
+ buf += k; /* full, get new unwritten data pointer */
+ size -= k; /* new data size */
+ i += k; /* new buffer data size */
+ }
+ /* This chunk of the buffer is full. See if can make some space by
+ * writing to the disk, if there's enough unprotected space to do so.
+ * Try to fill out any unaligned chunk, along with any subsequent full
+ * chunks that will fit in unprotected space.
+ */
+ /* any unprotected space we can write to? */
+ if (j = min (i,f->protect - f->filepos)) {
+ /* yes, filepos not at chunk boundary? */
+ if ((k = f->filepos % OVERFLOWBUFLEN) && ((k = OVERFLOWBUFLEN - k) < j))
+ j -= k; /* yes, and can write out partial chunk */
+ else k = 0; /* no partial chunk to write */
+ /* if at least a chunk free, write that too */
+ if (j > OVERFLOWBUFLEN) k += j - (j % OVERFLOWBUFLEN);
+ if (k) { /* write data if there is anything we can */
+ mmdf_phys_write (f,f->buf,k);
+ /* slide buffer */
+ if (i -= k) memmove (f->buf,f->buf + k,i);
+ f->bufpos = f->buf + i; /* new end of buffer */
+ }
+ }
+
+ /* Have flushed the buffer as best as possible. All done if no more
+ * data to write. Otherwise, if the buffer is empty AND if the unwritten
+ * data is larger than a chunk AND the unprotected space is also larger
+ * than a chunk, then write as many chunks as we can directly from the
+ * data. Buffer the rest, expanding the buffer as needed.
+ */
+ if (size) { /* have more data that we need to buffer? */
+ /* can write any of it to disk instead? */
+ if ((f->bufpos == f->buf) &&
+ ((j = min (f->protect - f->filepos,size)) > OVERFLOWBUFLEN)) {
+ /* write as much as we can right now */
+ mmdf_phys_write (f,buf,j -= (j % OVERFLOWBUFLEN));
+ buf += j; /* new data pointer */
+ size -= j; /* new data size */
+ f->curpos += j; /* advance current pointer */
+ }
+ if (size) { /* still have data that we need to buffer? */
+ /* yes, need to expand the buffer? */
+ if ((i = ((f->bufpos + size) - f->buf)) > f->buflen) {
+ /* note current position in buffer */
+ j = f->bufpos - f->buf;
+ i += OVERFLOWBUFLEN; /* yes, grow another chunk */
+ fs_resize ((void **) &f->buf,f->buflen = i - (i % OVERFLOWBUFLEN));
+ /* in case buffer relocated */
+ f->bufpos = f->buf + j;
+ }
+ /* buffer remaining data */
+ memcpy (f->bufpos,buf,size);
+ f->bufpos += size; /* new end of buffer */
+ f->curpos += size; /* advance current pointer */
+ }
+ }
+ }
+ else { /* flush buffer to disk */
+ mmdf_phys_write (f,f->buf,i = f->bufpos - f->buf);
+ f->bufpos = f->buf; /* reset buffer */
+ /* update positions */
+ f->curpos = f->protect = f->filepos;
+ }
+}
+
+/* Physical disk write
+ * Accepts: buffered file pointer
+ * buffer address
+ * buffer size
+ * Does not return until success
+ */
+
+void mmdf_phys_write (MMDFFILE *f,char *buf,size_t size)
+{
+ MAILSTREAM *stream = f->stream;
+ /* write data at desired position */
+ while (size && ((lseek (LOCAL->fd,f->filepos,L_SET) < 0) ||
+ (write (LOCAL->fd,buf,size) < 0))) {
+ int e;
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Unable to write to mailbox: %s",strerror (e = errno));
+ MM_LOG (tmp,ERROR);
+ MM_DISKERROR (NIL,e,T); /* serious problem, must retry */
+ }
+ f->filepos += size; /* update file position */
+}
diff --git a/imap/src/osdep/amiga/mtx.c b/imap/src/osdep/amiga/mtx.c
new file mode 100644
index 00000000..8e6f76e8
--- /dev/null
+++ b/imap/src/osdep/amiga/mtx.c
@@ -0,0 +1,1371 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MTX mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 22 May 1990
+ * Last Edited: 11 October 2007
+ */
+
+
+/* FILE TIME SEMANTICS
+ *
+ * The atime is the last read time of the file.
+ * The mtime is the last flags update time of the file.
+ * The ctime is the last write time of the file.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+/* MTX I/O stream local data */
+
+typedef struct mtx_local {
+ unsigned int shouldcheck: 1; /* if ping should do a check instead */
+ unsigned int mustcheck: 1; /* if ping must do a check instead */
+ int fd; /* file descriptor for I/O */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* last snarf time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+} MTXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MTXLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *mtx_valid (char *name);
+int mtx_isvalid (char *name,char *tmp);
+void *mtx_parameters (long function,void *value);
+void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void mtx_list (MAILSTREAM *stream,char *ref,char *pat);
+void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mtx_create (MAILSTREAM *stream,char *mailbox);
+long mtx_delete (MAILSTREAM *stream,char *mailbox);
+long mtx_rename (MAILSTREAM *stream,char *old,char *newname);
+long mtx_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *mtx_open (MAILSTREAM *stream);
+void mtx_close (MAILSTREAM *stream,long options);
+void mtx_flags (MAILSTREAM *stream,char *sequence,long flags);
+char *mtx_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void mtx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long mtx_ping (MAILSTREAM *stream);
+void mtx_check (MAILSTREAM *stream);
+void mtx_snarf (MAILSTREAM *stream);
+long mtx_expunge (MAILSTREAM *stream,char *sequence,long options);
+long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+char *mtx_file (char *dst,char *name);
+long mtx_parse (MAILSTREAM *stream);
+MESSAGECACHE *mtx_elt (MAILSTREAM *stream,unsigned long msgno);
+void mtx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
+void mtx_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag);
+unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size);
+
+/* MTX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mtxdriver = {
+ "mtx", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_CRLF|DR_NOSTICKY|DR_LOCKING,
+ (DRIVER *) NIL, /* next driver */
+ mtx_valid, /* mailbox is valid for us */
+ mtx_parameters, /* manipulate parameters */
+ mtx_scan, /* scan mailboxes */
+ mtx_list, /* list mailboxes */
+ mtx_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ mtx_delete, /* delete mailbox */
+ mtx_rename, /* rename mailbox */
+ mtx_status, /* status of mailbox */
+ mtx_open, /* open mailbox */
+ mtx_close, /* close mailbox */
+ mtx_flags, /* fetch message "fast" attributes */
+ mtx_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mtx_header, /* fetch message header */
+ mtx_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ mtx_flag, /* modify flags */
+ mtx_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mtx_ping, /* ping mailbox to see if still alive */
+ mtx_check, /* check for new messages */
+ mtx_expunge, /* expunge deleted messages */
+ mtx_copy, /* copy messages to another mailbox */
+ mtx_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mtxproto = {&mtxdriver};
+
+/* MTX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mtx_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return mtx_isvalid (name,tmp) ? &mtxdriver : NIL;
+}
+
+
+/* MTX mail test for valid mailbox
+ * Accepts: mailbox name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int mtx_isvalid (char *name,char *tmp)
+{
+ int fd;
+ int ret = NIL;
+ char *s,file[MAILTMPLEN];
+ struct stat sbuf;
+ time_t tp[2];
+ errno = EINVAL; /* assume invalid argument */
+ /* if file, get its status */
+ if ((s = mtx_file (file,name)) && !stat (s,&sbuf)) {
+ if (!sbuf.st_size) { /* allow empty file if INBOX */
+ if ((s = mailboxfile (tmp,name)) && !*s) ret = T;
+ else errno = 0; /* empty file */
+ }
+ else if ((fd = open (file,O_RDONLY,NIL)) >= 0) {
+ memset (tmp,'\0',MAILTMPLEN);
+ if ((read (fd,tmp,64) >= 0) && (s = strchr (tmp,'\015')) &&
+ (s[1] == '\012')) { /* valid format? */
+ *s = '\0'; /* tie off header */
+ /* must begin with dd-mmm-yy" */
+ ret = (((tmp[2] == '-' && tmp[6] == '-') ||
+ (tmp[1] == '-' && tmp[5] == '-')) &&
+ (s = strchr (tmp+18,',')) && strchr (s+2,';')) ? T : NIL;
+ }
+ else errno = -1; /* bogus format */
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if (sbuf.st_ctime > sbuf.st_atime) {
+ tp[0] = sbuf.st_atime; /* preserve atime and mtime */
+ tp[1] = sbuf.st_mtime;
+ utime (file,tp); /* set the times */
+ }
+ }
+ }
+ /* in case INBOX but not mtx format */
+ else if ((errno == ENOENT) && !compare_cstring (name,"INBOX")) errno = -1;
+ return ret; /* return what we should */
+}
+
+/* MTX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mtx_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = mtx_file ((char *) value,"INBOX");
+ break;
+ }
+ return ret;
+}
+
+
+/* MTX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MTX mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mtx_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MTX mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MTX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return mtx_rename (stream,mailbox,NIL);
+}
+
+
+/* MTX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = T;
+ char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ if (!mtx_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'/')) && !s[1])))) {
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else if ((fd = open (file,O_RDWR,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get exclusive parse/append permission */
+ if ((ld = lockfd (fd,lock,LOCK_EX)) < 0) {
+ MM_LOG ("Unable to lock rename mailbox",ERROR);
+ return NIL;
+ }
+ /* lock out other users */
+ if (flock (fd,LOCK_EX|LOCK_NB)) {
+ close (fd); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ MM_LOG (tmp,ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return NIL;
+ }
+
+ if (newname) { /* want rename? */
+ if (s = strrchr (tmp,'/')) {/* found superior to destination name? */
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp,get_dir_protection (newname)))
+ ret = NIL;
+ else *s = c; /* restore full name */
+ }
+ /* rename the file */
+ if (ret && rename (file,tmp)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ else if (unlink (file)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* recreate file if renamed INBOX */
+ if (ret && !compare_cstring (old,"INBOX")) dummy_create (NIL,"INBOX.MTX");
+ return ret; /* return success */
+}
+
+/* Mtx Mail status
+ * Accepts: mail stream
+ * mailbox name
+ * status flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_status (MAILSTREAM *stream,char *mbx,long flags)
+{
+ MAILSTATUS status;
+ unsigned long i;
+ MAILSTREAM *tstream = NIL;
+ MAILSTREAM *systream = NIL;
+ /* make temporary stream (unless this mbx) */
+ if (!stream && !(stream = tstream =
+ mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
+ status.flags = flags; /* return status values */
+ status.messages = stream->nmsgs;
+ status.recent = stream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
+ if (!mail_elt (stream,i)->seen) status.unseen++;
+ status.uidnext = stream->uid_last + 1;
+ status.uidvalidity = stream->uid_validity;
+ /* calculate post-snarf results */
+ if (!status.recent && stream->inbox &&
+ (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) {
+ status.messages += systream->nmsgs;
+ status.recent += systream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1; i <= systream->nmsgs; i++)
+ if (!mail_elt (systream,i)->seen) status.unseen++;
+ /* kludge but probably good enough */
+ status.uidnext += systream->nmsgs;
+ }
+ MM_STATUS(stream,mbx,&status);/* pass status to main program */
+ if (tstream) mail_close (tstream);
+ if (systream) mail_close (systream);
+ return T; /* success */
+}
+
+/* MTX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mtx_open (MAILSTREAM *stream)
+{
+ int fd,ld;
+ char tmp[MAILTMPLEN];
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&mtxproto);
+ if (stream->local) fatal ("mtx recycle stream");
+ user_flags (stream); /* set up user flags */
+ /* canonicalize the mailbox name */
+ if (!mtx_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ MM_LOG (tmp,ERROR);
+ }
+ if (stream->rdonly ||
+ (fd = open (tmp,O_RDWR,NIL)) < 0) {
+ if ((fd = open (tmp,O_RDONLY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else if (!stream->rdonly) { /* got it, but readonly */
+ MM_LOG ("Can't get write access to mailbox, access is readonly",WARN);
+ stream->rdonly = T;
+ }
+ }
+ stream->local = fs_get (sizeof (MTXLOCAL));
+ LOCAL->fd = fd; /* bind the file */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ /* get shared parse permission */
+ if ((ld = lockfd (fd,tmp,LOCK_SH)) < 0) {
+ MM_LOG ("Unable to lock open mailbox",ERROR);
+ return NIL;
+ }
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH); /* lock the file */
+ (*bn) (BLOCK_NONE,NIL);
+ unlockfd (ld,tmp); /* release shared parse permission */
+ LOCAL->filesize = 0; /* initialize parsed file size */
+ /* time not set up yet */
+ LOCAL->lastsnarf = LOCAL->filetime = 0;
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (mtx_ping (stream) && !stream->nmsgs)
+ MM_LOG ("Mailbox is empty",(long) NIL);
+ if (!LOCAL) return NIL; /* failure if stream died */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft =
+ stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ return stream; /* return stream to caller */
+}
+
+/* MTX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mtx_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ if (options & CL_EXPUNGE) mtx_expunge (stream,NIL,NIL);
+ stream->silent = silent; /* restore previous status */
+ flock (LOCAL->fd,LOCK_UN); /* unlock local file */
+ close (LOCAL->fd); /* close the local file */
+ /* free local text buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+
+/* MTX mail fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ * Sniffs at file to see if some other process changed the flags
+ */
+
+void mtx_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ unsigned long i;
+ if (mtx_ping (stream) && /* ping mailbox, get new status for messages */
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if (mail_elt (stream,i)->sequence) mtx_elt (stream,i);
+}
+
+/* MTX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mtx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get to header position */
+ lseek (LOCAL->fd,mtx_hdrpos (stream,msgno,length),L_SET);
+ /* is buffer big enough? */
+ if (*length > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = *length) + 1);
+ }
+ LOCAL->buf[*length] = '\0'; /* tie off string */
+ /* slurp the data */
+ read (LOCAL->fd,LOCAL->buf,*length);
+ return (char *) LOCAL->buf;
+}
+
+/* MTX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: T, always
+ */
+
+long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ FDDATA d;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mtx_elt (stream,msgno); /* get message status */
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ elt->seen = T; /* mark message as seen */
+ /* recalculate status */
+ mtx_update_status (stream,msgno,NIL);
+ MM_FLAGS (stream,msgno);
+ }
+ /* find header position */
+ i = mtx_hdrpos (stream,msgno,&j);
+ d.fd = LOCAL->fd; /* set up file descriptor */
+ d.pos = i + j;
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE;
+ INIT (bs,fd_string,&d,elt->rfc822_size - j);
+ return T; /* success */
+}
+
+/* MTX mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ */
+
+void mtx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ time_t tp[2];
+ struct stat sbuf;
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* make sure read comes after all that */
+ utime (stream->mailbox,tp);
+ }
+}
+
+
+/* MTX mail per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ struct stat sbuf;
+ /* maybe need to do a checkpoint? */
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ LOCAL->filetime = 0; /* don't do this test for any other messages */
+ }
+ /* recalculate status */
+ mtx_update_status (stream,elt->msgno,NIL);
+}
+
+/* MTX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long mtx_ping (MAILSTREAM *stream)
+{
+ unsigned long i = 1;
+ long r = T;
+ int ld;
+ char lock[MAILTMPLEN];
+ struct stat sbuf;
+ if (stream && LOCAL) { /* only if stream already open */
+ fstat (LOCAL->fd,&sbuf); /* get current file poop */
+ if (LOCAL->filetime && !(LOCAL->mustcheck || LOCAL->shouldcheck) &&
+ (LOCAL->filetime < sbuf.st_mtime)) LOCAL->shouldcheck = T;
+ /* check for changed message status */
+ if (LOCAL->mustcheck || LOCAL->shouldcheck) {
+ LOCAL->filetime = sbuf.st_mtime;
+ if (LOCAL->shouldcheck) /* babble when we do this unilaterally */
+ MM_NOTIFY (stream,"[CHECK] Checking for flag updates",NIL);
+ while (i <= stream->nmsgs) mtx_elt (stream,i++);
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ }
+ /* get shared parse/append permission */
+ if ((sbuf.st_size != LOCAL->filesize) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) {
+ /* parse resulting mailbox */
+ r = (mtx_parse (stream)) ? T : NIL;
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ if (LOCAL) { /* stream must still be alive */
+ /* snarf if this is a read-write inbox */
+ if (stream->inbox && !stream->rdonly) {
+ mtx_snarf (stream);
+ fstat (LOCAL->fd,&sbuf);/* see if file changed now */
+ if ((sbuf.st_size != LOCAL->filesize) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) {
+ /* parse resulting mailbox */
+ r = (mtx_parse (stream)) ? T : NIL;
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ }
+ }
+ }
+ return r; /* return result of the parse */
+}
+
+
+/* MTX mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void mtx_check (MAILSTREAM *stream)
+{
+ /* mark that a check is desired */
+ if (LOCAL) LOCAL->mustcheck = T;
+ if (mtx_ping (stream)) MM_LOG ("Check completed",(long) NIL);
+}
+
+/* MTX mail snarf messages from system inbox
+ * Accepts: MAIL stream
+ */
+
+void mtx_snarf (MAILSTREAM *stream)
+{
+ unsigned long i = 0;
+ unsigned long j,r,hdrlen,txtlen;
+ struct stat sbuf;
+ char *hdr,*txt,lock[MAILTMPLEN],tmp[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ MAILSTREAM *sysibx = NIL;
+ int ld;
+ /* give up if can't get exclusive permission */
+ if ((time (0) >= (LOCAL->lastsnarf +
+ (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) &&
+ strcmp (sysinbox (),stream->mailbox) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) >= 0)) {
+ MM_CRITICAL (stream); /* go critical */
+ /* sizes match and anything in sysinbox? */
+ if (!stat (sysinbox (),&sbuf) && sbuf.st_size &&
+ !fstat (LOCAL->fd,&sbuf) && (sbuf.st_size == LOCAL->filesize) &&
+ (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
+ (!sysibx->rdonly) && (r = sysibx->nmsgs)) {
+ /* yes, go to end of file in our mailbox */
+ lseek (LOCAL->fd,sbuf.st_size,L_SET);
+ /* for each message in sysibx mailbox */
+ while (r && (++i <= sysibx->nmsgs)) {
+ /* snarf message from system INBOX */
+ hdr = cpystr (mail_fetchheader_full (sysibx,i,NIL,&hdrlen,NIL));
+ txt = mail_fetchtext_full (sysibx,i,&txtlen,FT_PEEK);
+ /* if have a message */
+ if (j = hdrlen + txtlen) {
+ /* calculate header line */
+ mail_date (LOCAL->buf,elt = mail_elt (sysibx,i));
+ sprintf (LOCAL->buf + strlen (LOCAL->buf),
+ ",%lu;0000000000%02o\015\012",j,(unsigned)
+ ((fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ /* copy message */
+ if ((write (LOCAL->fd,LOCAL->buf,strlen (LOCAL->buf)) < 0) ||
+ (write (LOCAL->fd,hdr,hdrlen) < 0) ||
+ (write (LOCAL->fd,txt,txtlen) < 0)) r = 0;
+ }
+ fs_give ((void **) &hdr);
+ }
+
+ /* make sure all the updates take */
+ if (fsync (LOCAL->fd)) r = 0;
+ if (r) { /* delete all the messages we copied */
+ if (r == 1) strcpy (tmp,"1");
+ else sprintf (tmp,"1:%lu",r);
+ mail_flag (sysibx,tmp,"\\Deleted",ST_SET);
+ mail_expunge (sysibx); /* now expunge all those messages */
+ }
+ else {
+ sprintf (LOCAL->buf,"Can't copy new mail: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,WARN);
+ ftruncate (LOCAL->fd,sbuf.st_size);
+ }
+ fstat (LOCAL->fd,&sbuf); /* yes, get current file size */
+ LOCAL->filetime = sbuf.st_mtime;
+ }
+ if (sysibx) mail_close (sysibx);
+ MM_NOCRITICAL (stream); /* release critical */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ LOCAL->lastsnarf = time (0);/* note time of last snarf */
+ }
+}
+
+/* MTX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long mtx_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ time_t tp[2];
+ struct stat sbuf;
+ off_t pos = 0;
+ int ld;
+ unsigned long i = 1;
+ unsigned long j,k,m,recent;
+ unsigned long n = 0;
+ unsigned long delta = 0;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (!(ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ mtx_ping (stream))); /* parse sequence if given, ping stream */
+ else if (stream->rdonly) MM_LOG ("Expunge ignored on readonly mailbox",WARN);
+ else {
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ }
+ /* The cretins who designed flock() created a window of vulnerability in
+ * upgrading locks from shared to exclusive or downgrading from exclusive
+ * to shared. Rather than maintain the lock at shared status at a minimum,
+ * flock() actually *releases* the former lock. Obviously they never talked
+ * to any database guys. Fortunately, we have the parse/append permission
+ * lock. If we require this lock before going exclusive on the mailbox,
+ * another process can not sneak in and steal the exclusive mailbox lock on
+ * us, because it will block on trying to get parse/append permission first.
+ */
+ /* get exclusive parse/append permission */
+ if ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) < 0)
+ MM_LOG ("Unable to lock expunge mailbox",ERROR);
+ /* make sure see any newly-arrived messages */
+ else if (!mtx_parse (stream));
+ /* get exclusive access */
+ else if (flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH);/* recover previous lock */
+ (*bn) (BLOCK_NONE,NIL);
+ MM_LOG ("Can't expunge because mailbox is in use by another process",
+ ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+
+ else {
+ MM_CRITICAL (stream); /* go critical */
+ recent = stream->recent; /* get recent now that pinged and locked */
+ /* for each message */
+ while (i <= stream->nmsgs) {
+ /* get cache element */
+ elt = mtx_elt (stream,i);
+ /* number of bytes to smash or preserve */
+ k = elt->private.special.text.size + elt->rfc822_size;
+ /* if need to expunge this message */
+ if (elt->deleted && (sequence ? elt->sequence : T)) {
+ /* if recent, note one less recent message */
+ if (elt->recent) --recent;
+ delta += k; /* number of bytes to delete */
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else if (i++ && delta) {/* preserved message */
+ /* first byte to preserve */
+ j = elt->private.special.offset;
+ do { /* read from source position */
+ m = min (k,LOCAL->buflen);
+ lseek (LOCAL->fd,j,L_SET);
+ read (LOCAL->fd,LOCAL->buf,m);
+ pos = j - delta; /* write to destination position */
+ lseek (LOCAL->fd,pos,L_SET);
+ while (T) {
+ lseek (LOCAL->fd,pos,L_SET);
+ if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ pos += m; /* new position */
+ j += m; /* next chunk, perhaps */
+ } while (k -= m); /* until done */
+ /* note the new address of this text */
+ elt->private.special.offset -= delta;
+ }
+ /* preserved but no deleted messages */
+ else pos = elt->private.special.offset + k;
+ }
+ if (n) { /* truncate file after last message */
+ if (pos != (LOCAL->filesize -= delta)) {
+ sprintf (LOCAL->buf,
+ "Calculated size mismatch %lu != %lu, delta = %lu",
+ (unsigned long) pos,(unsigned long) LOCAL->filesize,delta);
+ MM_LOG (LOCAL->buf,WARN);
+ LOCAL->filesize = pos;/* fix it then */
+ }
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ sprintf (LOCAL->buf,"Expunged %lu messages",n);
+ /* output the news */
+ MM_LOG (LOCAL->buf,(long) NIL);
+ }
+ else MM_LOG ("No messages deleted, so no update needed",(long) NIL);
+ fsync (LOCAL->fd); /* force disk update */
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* reset atime to now */
+ utime (stream->mailbox,tp);
+ MM_NOCRITICAL (stream); /* release critical */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH);/* allow sharers again */
+ (*bn) (BLOCK_NONE,NIL);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+ }
+ return ret;
+}
+
+/* MTX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ time_t tp[2];
+ MESSAGECACHE *elt;
+ unsigned long i,j,k;
+ long ret = LONGT;
+ int fd,ld;
+ char file[MAILTMPLEN],lock[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ /* make sure valid mailbox */
+ if (!mtx_isvalid (mailbox,LOCAL->buf)) switch (errno) {
+ case ENOENT: /* no such file? */
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid MTX-format mailbox name: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a MTX-format mailbox: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* got file? */
+ if ((fd = open (mtx_file (file,mailbox),O_RDWR,NIL)) < 0) {
+ sprintf (LOCAL->buf,"Unable to open copy mailbox: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ MM_CRITICAL (stream); /* go critical */
+ /* get exclusive parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) {
+ MM_LOG ("Unable to lock copy mailbox",ERROR);
+ MM_NOCRITICAL (stream);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
+
+ /* for each requested message */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ /* number of bytes to copy */
+ k = elt->private.special.text.size + elt->rfc822_size;
+ do { /* read from source position */
+ j = min (k,LOCAL->buflen);
+ read (LOCAL->fd,LOCAL->buf,j);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ } while (ret && (k -= j));/* until done */
+ }
+ /* make sure all the updates take */
+ if (!(ret && (ret = !fsync (fd)))) {
+ sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ }
+ if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */
+ /* else preserve \Marked status */
+ else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
+ tp[1] = sbuf.st_mtime; /* preserve mtime */
+ utime (file,tp); /* set the times */
+ close (fd); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ MM_NOCRITICAL (stream); /* release critical */
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE)) {
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mtx_elt (stream,i))->sequence) {
+ elt->deleted = T; /* mark message deleted */
+ /* recalculate status */
+ mtx_update_status (stream,i,NIL);
+ }
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* make sure atime remains greater */
+ utime (stream->mailbox,tp);
+ }
+ }
+ if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
+ MM_LOG ("Can not return meaningful COPYUID with this mailbox format",WARN);
+ return ret;
+}
+
+/* MTX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd,ld,c;
+ char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ time_t tp[2];
+ FILE *df;
+ MESSAGECACHE elt;
+ long f;
+ unsigned long i,uf;
+ STRING *message;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = user_flags (&mtxproto);
+ /* make sure valid mailbox */
+ if (!mtx_isvalid (mailbox,tmp)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!compare_cstring (mailbox,"INBOX")) dummy_create (NIL,"INBOX.MTX");
+ else {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* falls through */
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid MTX-format mailbox name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MTX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get first message */
+ if (!MM_APPEND (af) (stream,data,&flags,&date,&message)) return NIL;
+
+ /* open destination mailbox */
+ if (((fd = open (mtx_file (file,mailbox),O_WRONLY|O_APPEND,NIL)) < 0) ||
+ !(df = fdopen (fd,"ab"))) {
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) {
+ MM_LOG ("Unable to lock append mailbox",ERROR);
+ close (fd);
+ return NIL;
+ }
+ MM_CRITICAL (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ errno = 0;
+ do { /* parse flags */
+ if (!SIZE (message)) { /* guard against zero-length */
+ MM_LOG ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ f = mail_parse_flags (stream,flags,&i);
+ /* reverse bits (dontcha wish we had CIRC?) */
+ for (uf = 0; i; uf |= 1 << (29 - find_rightmost_bit (&i)));
+ if (date) { /* parse date if given */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* mark failure */
+ break;
+ }
+ mail_date (tmp,&elt); /* write preseved date */
+ }
+ else internal_date (tmp); /* get current date in IMAP format */
+ /* write header */
+ if (fprintf (df,"%s,%lu;%010lo%02lo\015\012",tmp,i = SIZE (message),uf,
+ (unsigned long) f) < 0) ret = NIL;
+ else { /* write message */
+ if (i) do c = 0xff & SNX (message);
+ while ((putc (c,df) != EOF) && --i);
+ /* get next message */
+ if (i || !MM_APPEND (af) (stream,data,&flags,&date,&message)) ret = NIL;
+ }
+ } while (ret && message);
+ /* if error... */
+ if (!ret || (fflush (df) == EOF)) {
+ ftruncate (fd,sbuf.st_size);/* revert file */
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ if (errno) {
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ ret = NIL;
+ }
+ if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */
+ /* else preserve \Marked status */
+ else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
+ tp[1] = sbuf.st_mtime; /* preserve mtime */
+ utime (file,tp); /* set the times */
+ fclose (df); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ MM_NOCRITICAL (stream); /* release critical */
+ if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
+ MM_LOG ("Can not return meaningful APPENDUID with this mailbox format",
+ WARN);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* MTX mail generate file string
+ * Accepts: temporary buffer to write into
+ * mailbox name string
+ * Returns: local file string or NIL if failure
+ */
+
+char *mtx_file (char *dst,char *name)
+{
+ char tmp[MAILTMPLEN];
+ char *s = mailboxfile (dst,name);
+ /* return our standard inbox */
+ return (s && !*s) ? mailboxfile (dst,mtx_isvalid ("~/INBOX",tmp) ?
+ "~/INBOX" : "INBOX.MTX") : s;
+}
+
+/* MTX mail parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long mtx_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = NIL;
+ unsigned char c,*s,*t,*x;
+ char tmp[MAILTMPLEN];
+ unsigned long i,j;
+ long curpos = LOCAL->filesize;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ short added = NIL;
+ short silent = stream->silent;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %lu to %lu!",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ stream->silent = T; /* don't pass up exists events yet */
+ while (sbuf.st_size - curpos){/* while there is stuff to parse */
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,L_SET);
+ if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
+ sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size,
+ i ? strerror (errno) : "no data read");
+ MM_LOG (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
+ if (!((s = strchr (LOCAL->buf,'\015')) && (s[1] == '\012'))) {
+ sprintf (tmp,"Unable to find CRLF at %lu in %lu bytes, text: %s",
+ (unsigned long) curpos,i,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ *s = '\0'; /* tie off header line */
+ i = (s + 2) - LOCAL->buf; /* note start of text offset */
+ if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
+ sprintf (tmp,"Unable to parse internal header at %lu: %s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ *s++ = '\0'; *t++ = '\0'; /* tie off fields */
+
+ added = T; /* note that a new message was added */
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ /* instantiate an elt for this message */
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ elt->private.uid = ++stream->uid_last;
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* in case error */
+ elt->private.special.text.size = 0;
+ /* header size not known yet */
+ elt->private.msg.header.text.size = 0;
+ x = s; /* parse the header components */
+ if (mail_parse_date (elt,LOCAL->buf) &&
+ (elt->rfc822_size = strtoul (s,(char **) &s,10)) && (!(s && *s)) &&
+ isdigit (t[0]) && isdigit (t[1]) && isdigit (t[2]) &&
+ isdigit (t[3]) && isdigit (t[4]) && isdigit (t[5]) &&
+ isdigit (t[6]) && isdigit (t[7]) && isdigit (t[8]) &&
+ isdigit (t[9]) && isdigit (t[10]) && isdigit (t[11]) && !t[12])
+ elt->private.special.text.size = i;
+ else { /* oops */
+ sprintf (tmp,"Unable to parse internal header elements at %ld: %s,%s;%s",
+ curpos,(char *) LOCAL->buf,(char *) x,(char *) t);
+ MM_LOG (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ /* make sure didn't run off end of file */
+ if ((curpos += (elt->rfc822_size + i)) > sbuf.st_size) {
+ sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
+ elt->private.special.offset,(unsigned long) curpos,
+ (unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ c = t[10]; /* remember first system flags byte */
+ t[10] = '\0'; /* tie off flags */
+ j = strtoul (t,NIL,8); /* get user flags value */
+ t[10] = c; /* restore first system flags byte */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ /* calculate system flags */
+ if ((j = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T;
+ if (j & fDELETED) elt->deleted = T;
+ if (j & fFLAGGED) elt->flagged = T;
+ if (j & fANSWERED) elt->answered = T;
+ if (j & fDRAFT) elt->draft = T;
+ if (!(j & fOLD)) { /* newly arrived message? */
+ elt->recent = T;
+ recent++; /* count up a new recent message */
+ /* mark it as old */
+ mtx_update_status (stream,nmsgs,NIL);
+ }
+ }
+ fsync (LOCAL->fd); /* make sure all the fOLD flags take */
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
+ LOCAL->filetime = sbuf.st_mtime;
+ if (added && !stream->rdonly){/* make sure atime updated */
+ time_t tp[2];
+ tp[0] = time (0);
+ tp[1] = LOCAL->filetime;
+ utime (stream->mailbox,tp);
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return LONGT; /* return the winnage */
+}
+
+/* MTX get cache element with status updating from file
+ * Accepts: MAIL stream
+ * message number
+ * Returns: cache element
+ */
+
+MESSAGECACHE *mtx_elt (MAILSTREAM *stream,unsigned long msgno)
+{
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
+ old.answered = elt->answered; old.draft = elt->draft;
+ old.user_flags = elt->user_flags;
+ mtx_read_flags (stream,elt);
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
+ (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
+ MM_FLAGS (stream,msgno); /* let top level know */
+ return elt;
+}
+
+/* MTX read flags from file
+ * Accepts: MAIL stream
+ * Returns: cache element
+ */
+
+void mtx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ unsigned long i,j;
+ /* noop if readonly and have valid flags */
+ if (stream->rdonly && elt->valid) return;
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 14,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,12) < 0) {
+ sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ /* calculate system flags */
+ i = (((LOCAL->buf[10]-'0') * 8) + LOCAL->buf[11]-'0');
+ elt->seen = i & fSEEN ? T : NIL; elt->deleted = i & fDELETED ? T : NIL;
+ elt->flagged = i & fFLAGGED ? T : NIL;
+ elt->answered = i & fANSWERED ? T : NIL; elt->draft = i & fDRAFT ? T : NIL;
+ LOCAL->buf[10] = '\0'; /* tie off flags */
+ j = strtoul(LOCAL->buf,NIL,8);/* get user flags value */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ elt->valid = T; /* have valid flags now */
+}
+
+/* MTX update status string
+ * Accepts: MAIL stream
+ * message number
+ * flag saying whether or not to sync
+ */
+
+void mtx_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag)
+{
+ time_t tp[2];
+ struct stat sbuf;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ unsigned long j,k = 0;
+ /* readonly */
+ if (stream->rdonly || !elt->valid) mtx_read_flags (stream,elt);
+ else { /* readwrite */
+ j = elt->user_flags; /* get user flags */
+ /* reverse bits (dontcha wish we had CIRC?) */
+ while (j) k |= 1 << (29 - find_rightmost_bit (&j));
+ /* print new flag string */
+ sprintf (LOCAL->buf,"%010lo%02o",k,(unsigned)
+ (fOLD + (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ /* get to that place in the file */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 14,L_SET);
+ /* write new flags */
+ write (LOCAL->fd,LOCAL->buf,12);
+ if (syncflag) { /* sync if requested */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* make sure read is later */
+ utime (stream->mailbox,tp);
+ }
+ }
+}
+
+/* MTX locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * Returns: position of header in file
+ */
+
+unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size)
+{
+ unsigned long siz;
+ long i = 0;
+ int q = 0;
+ char *s,tmp[MAILTMPLEN];
+ MESSAGECACHE *elt = mtx_elt (stream,msgno);
+ unsigned long ret = elt->private.special.offset +
+ elt->private.special.text.size;
+ /* is header size known? */
+ if (!(*size = elt->private.msg.header.text.size)) {
+ lseek (LOCAL->fd,ret,L_SET);/* get to header position */
+ /* search message for CRLF CRLF */
+ for (siz = 1,s = tmp; siz <= elt->rfc822_size; siz++) {
+ /* read another buffer as necessary */
+ if ((--i <= 0) && /* buffer empty? */
+ (read (LOCAL->fd,s = tmp,
+ i = min (elt->rfc822_size - siz,(long) MAILTMPLEN)) < 0))
+ return ret; /* I/O error? */
+ switch (q) { /* sniff at buffer */
+ case 0: /* first character */
+ q = (*s++ == '\015') ? 1 : 0;
+ break;
+ case 1: /* second character */
+ q = (*s++ == '\012') ? 2 : 0;
+ break;
+ case 2: /* third character */
+ q = (*s++ == '\015') ? 3 : 0;
+ break;
+ case 3: /* fourth character */
+ if (*s++ == '\012') { /* have the sequence? */
+ /* yes, note for later */
+ elt->private.msg.header.text.size = *size = siz;
+ return ret;
+ }
+ q = 0; /* lost... */
+ break;
+ }
+ }
+ /* header consumes entire message */
+ elt->private.msg.header.text.size = *size = elt->rfc822_size;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/amiga/mx.c b/imap/src/osdep/amiga/mx.c
new file mode 100644
index 00000000..45495279
--- /dev/null
+++ b/imap/src/osdep/amiga/mx.c
@@ -0,0 +1,1287 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MX mail routines
+ *
+ * Author(s): Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 3 May 1996
+ * Last Edited: 6 January 2008
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+/* Index file */
+
+#define MXINDEXNAME "/.mxindex"
+#define MXINDEX(d,s) strcat (mx_file (d,s),MXINDEXNAME)
+
+
+/* MX I/O stream local data */
+
+typedef struct mx_local {
+ int fd; /* file descriptor of open index */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned long cachedtexts; /* total size of all cached texts */
+ time_t scantime; /* last time directory scanned */
+} MXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MXLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *mx_valid (char *name);
+int mx_isvalid (char *name,char *tmp);
+int mx_namevalid (char *name);
+void *mx_parameters (long function,void *value);
+long mx_dirfmttest (char *name);
+void mx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+long mx_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz);
+void mx_list (MAILSTREAM *stream,char *ref,char *pat);
+void mx_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mx_subscribe (MAILSTREAM *stream,char *mailbox);
+long mx_unsubscribe (MAILSTREAM *stream,char *mailbox);
+long mx_create (MAILSTREAM *stream,char *mailbox);
+long mx_delete (MAILSTREAM *stream,char *mailbox);
+long mx_rename (MAILSTREAM *stream,char *old,char *newname);
+int mx_rename_work (char *src,size_t srcl,char *dst,size_t dstl,char *name);
+MAILSTREAM *mx_open (MAILSTREAM *stream);
+void mx_close (MAILSTREAM *stream,long options);
+void mx_fast (MAILSTREAM *stream,char *sequence,long flags);
+char *mx_fast_work (MAILSTREAM *stream,MESSAGECACHE *elt);
+char *mx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags);
+long mx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void mx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void mx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long mx_ping (MAILSTREAM *stream);
+void mx_check (MAILSTREAM *stream);
+long mx_expunge (MAILSTREAM *stream,char *sequence,long options);
+long mx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,
+ long options);
+long mx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+long mx_append_msg (MAILSTREAM *stream,char *flags,MESSAGECACHE *elt,
+ STRING *st,SEARCHSET *set);
+
+int mx_select (struct direct *name);
+int mx_numsort (const void *d1,const void *d2);
+char *mx_file (char *dst,char *name);
+long mx_lockindex (MAILSTREAM *stream);
+void mx_unlockindex (MAILSTREAM *stream);
+void mx_setdate (char *file,MESSAGECACHE *elt);
+
+
+/* MX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mxdriver = {
+ "mx", /* driver name */
+ /* driver flags */
+ DR_MAIL|DR_LOCAL|DR_NOFAST|DR_CRLF|DR_LOCKING|DR_DIRFMT,
+ (DRIVER *) NIL, /* next driver */
+ mx_valid, /* mailbox is valid for us */
+ mx_parameters, /* manipulate parameters */
+ mx_scan, /* scan mailboxes */
+ mx_list, /* find mailboxes */
+ mx_lsub, /* find subscribed mailboxes */
+ mx_subscribe, /* subscribe to mailbox */
+ mx_unsubscribe, /* unsubscribe from mailbox */
+ mx_create, /* create mailbox */
+ mx_delete, /* delete mailbox */
+ mx_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ mx_open, /* open mailbox */
+ mx_close, /* close mailbox */
+ mx_fast, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mx_header, /* fetch message header only */
+ mx_text, /* fetch message body only */
+ NIL, /* fetch partial message test */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ mx_flag, /* modify flags */
+ mx_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mx_ping, /* ping mailbox to see if still alive */
+ mx_check, /* check for new messages */
+ mx_expunge, /* expunge deleted messages */
+ mx_copy, /* copy messages to another mailbox */
+ mx_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mxproto = {&mxdriver};
+
+/* MX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mx_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return mx_isvalid (name,tmp) ? &mxdriver : NIL;
+}
+
+
+/* MX mail test for valid mailbox
+ * Accepts: mailbox name
+ * temporary buffer to use
+ * Returns: T if valid, NIL otherwise with errno holding dir stat error
+ */
+
+int mx_isvalid (char *name,char *tmp)
+{
+ struct stat sbuf;
+ errno = NIL; /* zap error */
+ if ((strlen (name) <= NETMAXMBX) && *mx_file (tmp,name) &&
+ !stat (tmp,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
+ /* name is directory; is it mx? */
+ if (!stat (MXINDEX (tmp,name),&sbuf) &&
+ ((sbuf.st_mode & S_IFMT) == S_IFREG)) return T;
+ errno = NIL; /* directory but not mx */
+ }
+ else if (!compare_cstring (name,"INBOX")) errno = NIL;
+ return NIL;
+}
+
+
+/* MX mail test for valid mailbox
+ * Accepts: mailbox name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int mx_namevalid (char *name)
+{
+ char *s = (*name == '/') ? name + 1 : name;
+ while (s && *s) { /* make sure valid name */
+ if (isdigit (*s)) s++; /* digit, check this node further... */
+ else if (*s == '/') break; /* all digit node, barf */
+ /* non-digit, skip to next node or return */
+ else if (!((s = strchr (s+1,'/')) && *++s)) return T;
+ }
+ return NIL; /* all numeric or empty node */
+}
+
+/* MX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mx_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = mailboxfile ((char *) value,"~/INBOX");
+ break;
+ case GET_DIRFMTTEST:
+ ret = (void *) mx_dirfmttest;
+ break;
+ case GET_SCANCONTENTS:
+ ret = (void *) mx_scan_contents;
+ break;
+ }
+ return ret;
+}
+
+
+/* MX test for directory format internal node
+ * Accepts: candidate node name
+ * Returns: T if internal name, NIL otherwise
+ */
+
+long mx_dirfmttest (char *name)
+{
+ int c;
+ /* success if index name or all-numberic */
+ if (strcmp (name,MXINDEXNAME+1))
+ while (c = *name++) if (!isdigit (c)) return NIL;
+ return LONGT;
+}
+
+/* MX scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MX scan mailbox for contents
+ * Accepts: mailbox name
+ * desired contents
+ * contents size
+ * file size (ignored)
+ * Returns: NIL if contents not found, T if found
+ */
+
+long mx_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz)
+{
+ long i,nfiles;
+ void *a;
+ char *s;
+ long ret = NIL;
+ size_t namelen = strlen (name);
+ struct stat sbuf;
+ struct direct **names = NIL;
+ if ((nfiles = scandir (name,&names,mx_select,mx_numsort)) > 0)
+ for (i = 0; i < nfiles; ++i) {
+ if (!ret) {
+ sprintf (s = (char *) fs_get (namelen + strlen (names[i]->d_name) + 2),
+ "%s/%s",name,names[i]->d_name);
+ if (!stat (s,&sbuf) && (csiz <= sbuf.st_size))
+ ret = dummy_scan_contents (s,contents,csiz,sbuf.st_size);
+ fs_give ((void **) &s);
+ }
+ fs_give ((void **) &names[i]);
+ }
+ /* free directory list */
+ if (a = (void *) names) fs_give ((void **) &a);
+ return ret;
+}
+
+/* MX list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mx_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MX list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mx_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MX mail subscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to add to subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long mx_subscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return sm_subscribe (mailbox);
+}
+
+
+/* MX mail unsubscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to delete from subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long mx_unsubscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return sm_unsubscribe (mailbox);
+}
+
+/* MX mail create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mx_create (MAILSTREAM *stream,char *mailbox)
+{
+ DRIVER *test;
+ int fd;
+ char *s,tmp[MAILTMPLEN];
+ int mask = umask (0);
+ long ret = NIL;
+ if (!mx_namevalid (mailbox)) /* validate name */
+ sprintf (tmp,"Can't create mailbox %.80s: invalid MX-format name",mailbox);
+ /* must not already exist */
+ else if ((test = mail_valid (NIL,mailbox,NIL)) &&
+ strcmp (test->name,"dummy"))
+ sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox);
+ /* create directory */
+ else if (!dummy_create_path (stream,MXINDEX (tmp,mailbox),
+ get_dir_protection (mailbox)))
+ sprintf (tmp,"Can't create mailbox %.80s: %s",mailbox,strerror (errno));
+ else { /* success */
+ /* set index protection */
+ set_mbx_protections (mailbox,tmp);
+ /* tie off directory name */
+ *(s = strrchr (tmp,'/') + 1) = '\0';
+ /* set directory protection */
+ set_mbx_protections (mailbox,tmp);
+ ret = LONGT;
+ }
+ umask (mask); /* restore mask */
+ if (!ret) MM_LOG (tmp,ERROR); /* some error */
+ return ret;
+}
+
+/* MX mail delete mailbox
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mx_delete (MAILSTREAM *stream,char *mailbox)
+{
+ DIR *dirp;
+ struct direct *d;
+ char *s;
+ char tmp[MAILTMPLEN];
+ if (!mx_isvalid (mailbox,tmp))
+ sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox);
+ /* delete index */
+ else if (unlink (MXINDEX (tmp,mailbox)))
+ sprintf (tmp,"Can't delete mailbox %.80s index: %s",
+ mailbox,strerror (errno));
+ else { /* get directory name */
+ *(s = strrchr (tmp,'/')) = '\0';
+ if (dirp = opendir (tmp)) { /* open directory */
+ *s++ = '/'; /* restore delimiter */
+ /* massacre messages */
+ while (d = readdir (dirp)) if (mx_select (d)) {
+ strcpy (s,d->d_name); /* make path */
+ unlink (tmp); /* sayonara */
+ }
+ closedir (dirp); /* flush directory */
+ *(s = strrchr (tmp,'/')) = '\0';
+ if (rmdir (tmp)) { /* try to remove the directory */
+ sprintf (tmp,"Can't delete name %.80s: %s",mailbox,strerror (errno));
+ MM_LOG (tmp,WARN);
+ }
+ }
+ return T; /* always success */
+ }
+ MM_LOG (tmp,ERROR); /* something failed */
+ return NIL;
+}
+
+/* MX mail rename mailbox
+ * Accepts: MX mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long mx_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN];
+ struct stat sbuf;
+ if (!mx_isvalid (old,tmp))
+ sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old);
+ else if (!mx_namevalid (newname))
+ sprintf (tmp,"Can't rename to mailbox %.80s: invalid MX-format name",
+ newname);
+ /* new mailbox name must not be valid */
+ else if (mx_isvalid (newname,tmp))
+ sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists",
+ newname);
+ else {
+ mx_file (tmp,old); /* build old directory name */
+ mx_file (tmp1,newname); /* and new directory name */
+ /* easy if not INBOX */
+ if (compare_cstring (old,"INBOX")) {
+ /* found superior to destination name? */
+ if (s = strrchr (mx_file (tmp1,newname),'/')) {
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp1,get_dir_protection (newname)))
+ return NIL;
+ *s = c; /* restore full name */
+ }
+ if (!rename (tmp,tmp1)) return LONGT;
+ }
+
+ /* RFC 3501 requires this */
+ else if (dummy_create_path (stream,strcat (tmp1,"/"),
+ get_dir_protection (newname))) {
+ void *a;
+ int i,n,lasterror;
+ struct direct **names = NIL;
+ size_t srcl = strlen (tmp);
+ size_t dstl = strlen (tmp1);
+ /* rename each mx file to new directory */
+ for (i = lasterror = 0,n = scandir (tmp,&names,mx_select,mx_numsort);
+ i < n; ++i) {
+ if (mx_rename_work (tmp,srcl,tmp1,dstl,names[i]->d_name))
+ lasterror = errno;
+ fs_give ((void **) &names[i]);
+ }
+ /* free directory list */
+ if (a = (void *) names) fs_give ((void **) &a);
+ if (lasterror || mx_rename_work (tmp,srcl,tmp1,dstl,MXINDEXNAME+1))
+ errno = lasterror;
+ else return mx_create (NIL,"INBOX");
+ }
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",
+ old,newname,strerror (errno));
+ }
+ MM_LOG (tmp,ERROR); /* something failed */
+ return NIL;
+}
+
+
+/* MX rename worker routine
+ * Accepts: source directory name
+ * source directory name length
+ * destination directory name
+ * destination directory name length
+ * name of node to move
+ * Returns: zero if success, non-zero if error
+ */
+
+int mx_rename_work (char *src,size_t srcl,char *dst,size_t dstl,char *name)
+{
+ int ret;
+ size_t len = strlen (name);
+ char *s = (char *) fs_get (srcl + len + 2);
+ char *d = (char *) fs_get (dstl + len + 1);
+ sprintf (s,"%s/%s",src,name);
+ sprintf (d,"%s%s",dst,name);
+ ret = rename (s,d);
+ fs_give ((void **) &s);
+ fs_give ((void **) &d);
+ return ret;
+}
+
+/* MX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mx_open (MAILSTREAM *stream)
+{
+ char tmp[MAILTMPLEN];
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&mxproto);
+ if (stream->local) fatal ("mx recycle stream");
+ stream->local = fs_get (sizeof (MXLOCAL));
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ mx_file (tmp,stream->mailbox);/* get directory name */
+ /* canonicalize mailbox name */
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ /* make temporary buffer */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ LOCAL->scantime = 0; /* not scanned yet */
+ LOCAL->fd = -1; /* no index yet */
+ LOCAL->cachedtexts = 0; /* no cached texts */
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (mx_ping (stream) && !(stream->nmsgs || stream->silent))
+ MM_LOG ("Mailbox is empty",(long) NIL);
+ stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
+ stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ stream->kwd_create = (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ?
+ NIL : T; /* can we create new user flags? */
+ return stream; /* return stream to caller */
+}
+
+/* MX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mx_close (MAILSTREAM *stream,long options)
+{
+ if (LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ if (options & CL_EXPUNGE) mx_expunge (stream,NIL,NIL);
+ /* free local scratch buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ stream->silent = silent; /* reset silent state */
+ }
+}
+
+/* MX mail fetch fast information
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ */
+
+void mx_fast (MAILSTREAM *stream,char *sequence,long flags)
+{
+ unsigned long i;
+ MESSAGECACHE *elt;
+ if (stream && LOCAL &&
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) mx_fast_work (stream,elt);
+}
+
+
+/* MX mail fetch fast information
+ * Accepts: MAIL stream
+ * message cache element
+ * Returns: name of message file
+ */
+
+char *mx_fast_work (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ struct stat sbuf;
+ struct tm *tm;
+ /* build message file name */
+ sprintf (LOCAL->buf,"%s/%lu",stream->mailbox,elt->private.uid);
+ /* have size yet? */
+ if (!elt->rfc822_size && !stat (LOCAL->buf,&sbuf)) {
+ /* make plausible IMAPish date string */
+ tm = gmtime (&sbuf.st_mtime);
+ elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
+ elt->year = tm->tm_year + 1900 - BASEYEAR;
+ elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
+ elt->seconds = tm->tm_sec;
+ elt->zhours = 0; elt->zminutes = 0; elt->zoccident = 0;
+ elt->rfc822_size = sbuf.st_size;
+ }
+ return (char *) LOCAL->buf; /* return file name */
+}
+
+/* MX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ unsigned long i;
+ int fd;
+ MESSAGECACHE *elt;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get elt */
+ if (!elt->private.msg.header.text.data) {
+ /* purge cache if too big */
+ if (LOCAL->cachedtexts > max (stream->nmsgs * 4096,2097152)) {
+ mail_gc (stream,GC_TEXTS);/* just can't keep that much */
+ LOCAL->cachedtexts = 0;
+ }
+ if ((fd = open (mx_fast_work (stream,elt),O_RDONLY,NIL)) < 0) return "";
+ /* is buffer big enough? */
+ if (elt->rfc822_size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = elt->rfc822_size) + 1);
+ }
+ /* slurp message */
+ read (fd,LOCAL->buf,elt->rfc822_size);
+ /* tie off file */
+ LOCAL->buf[elt->rfc822_size] = '\0';
+ close (fd); /* flush message file */
+ /* find end of header */
+ if (elt->rfc822_size < 4) i = 0;
+ else for (i = 4; (i < elt->rfc822_size) &&
+ !((LOCAL->buf[i - 4] == '\015') &&
+ (LOCAL->buf[i - 3] == '\012') &&
+ (LOCAL->buf[i - 2] == '\015') &&
+ (LOCAL->buf[i - 1] == '\012')); i++);
+ /* copy header */
+ cpytxt (&elt->private.msg.header.text,LOCAL->buf,i);
+ cpytxt (&elt->private.msg.text.text,LOCAL->buf+i,elt->rfc822_size - i);
+ /* add to cached size */
+ LOCAL->cachedtexts += elt->rfc822_size;
+ }
+ *length = elt->private.msg.header.text.size;
+ return (char *) elt->private.msg.header.text.data;
+}
+
+/* MX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ unsigned long i;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);
+ /* snarf message if don't have it yet */
+ if (!elt->private.msg.text.text.data) {
+ mx_header (stream,msgno,&i,flags);
+ if (!elt->private.msg.text.text.data) return NIL;
+ }
+ /* mark as seen */
+ if (!(flags & FT_PEEK) && mx_lockindex (stream)) {
+ elt->seen = T;
+ mx_unlockindex (stream);
+ MM_FLAGS (stream,msgno);
+ }
+ INIT (bs,mail_string,elt->private.msg.text.text.data,
+ elt->private.msg.text.text.size);
+ return T;
+}
+
+/* MX mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ */
+
+void mx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ mx_unlockindex (stream); /* finished with index */
+}
+
+
+/* MX per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void mx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ mx_lockindex (stream); /* lock index if not already locked */
+}
+
+/* MX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long mx_ping (MAILSTREAM *stream)
+{
+ MAILSTREAM *sysibx = NIL;
+ MESSAGECACHE *elt,*selt;
+ struct stat sbuf;
+ char *s,tmp[MAILTMPLEN];
+ int fd;
+ unsigned long i,j,r,old;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ int silent = stream->silent;
+ if (stat (stream->mailbox,&sbuf)) return NIL;
+ stream->silent = T; /* don't pass up exists events yet */
+ if (sbuf.st_ctime != LOCAL->scantime) {
+ struct direct **names = NIL;
+ long nfiles = scandir (stream->mailbox,&names,mx_select,mx_numsort);
+ if (nfiles < 0) nfiles = 0; /* in case error */
+ old = stream->uid_last;
+ /* note scanned now */
+ LOCAL->scantime = sbuf.st_ctime;
+ /* scan directory */
+ for (i = 0; i < nfiles; ++i) {
+ /* if newly seen, add to list */
+ if ((j = atoi (names[i]->d_name)) > old) {
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ stream->uid_last = (elt = mail_elt (stream,nmsgs))->private.uid = j;
+ elt->valid = T; /* note valid flags */
+ if (old) { /* other than the first pass? */
+ elt->recent = T; /* yup, mark as recent */
+ recent++; /* bump recent count */
+ }
+ }
+ fs_give ((void **) &names[i]);
+ }
+ /* free directory */
+ if (s = (void *) names) fs_give ((void **) &s);
+ }
+ stream->nmsgs = nmsgs; /* don't upset mail_uid() */
+
+ /* if INBOX, snarf from system INBOX */
+ if (mx_lockindex (stream) && stream->inbox &&
+ !strcmp (sysinbox (),stream->mailbox)) {
+ old = stream->uid_last;
+ MM_CRITICAL (stream); /* go critical */
+ /* see if anything in system inbox */
+ if (!stat (sysinbox (),&sbuf) && sbuf.st_size &&
+ (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
+ !sysibx->rdonly && (r = sysibx->nmsgs)) {
+ for (i = 1; i <= r; ++i) {/* for each message in sysinbox mailbox */
+ /* build file name we will use */
+ sprintf (LOCAL->buf,"%s/%lu",stream->mailbox,++old);
+ /* snarf message from Berkeley mailbox */
+ selt = mail_elt (sysibx,i);
+ if (((fd = open (LOCAL->buf,O_WRONLY|O_CREAT|O_EXCL,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL)))
+ >= 0) &&
+ (s = mail_fetchheader_full (sysibx,i,NIL,&j,FT_PEEK)) &&
+ (write (fd,s,j) == j) &&
+ (s = mail_fetchtext_full (sysibx,i,&j,FT_PEEK)) &&
+ (write (fd,s,j) == j) && !fsync (fd) && !close (fd)) {
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ stream->uid_last = /* create new elt, note its file number */
+ (elt = mail_elt (stream,nmsgs))->private.uid = old;
+ recent++; /* bump recent count */
+ /* set up initial flags and date */
+ elt->valid = elt->recent = T;
+ elt->seen = selt->seen;
+ elt->deleted = selt->deleted;
+ elt->flagged = selt->flagged;
+ elt->answered = selt->answered;
+ elt->draft = selt->draft;
+ elt->day = selt->day;elt->month = selt->month;elt->year = selt->year;
+ elt->hours = selt->hours;elt->minutes = selt->minutes;
+ elt->seconds = selt->seconds;
+ elt->zhours = selt->zhours; elt->zminutes = selt->zminutes;
+ elt->zoccident = selt->zoccident;
+ mx_setdate (LOCAL->buf,elt);
+ sprintf (tmp,"%lu",i);/* delete it from the sysinbox */
+ mail_flag (sysibx,tmp,"\\Deleted",ST_SET);
+ }
+ else { /* failed to snarf */
+ if (fd) { /* did it ever get opened? */
+ close (fd); /* close descriptor */
+ unlink (LOCAL->buf);/* flush this file */
+ }
+ sprintf (tmp,"Message copy to MX mailbox failed: %.80s",
+ s,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ r = 0; /* stop the snarf in its tracks */
+ }
+ }
+ /* update scan time */
+ if (!stat (stream->mailbox,&sbuf)) LOCAL->scantime = sbuf.st_ctime;
+ mail_expunge (sysibx); /* now expunge all those messages */
+ }
+ if (sysibx) mail_close (sysibx);
+ MM_NOCRITICAL (stream); /* release critical */
+ }
+ mx_unlockindex (stream); /* done with index */
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of mailbox size */
+ mail_recent (stream,recent);
+ return T; /* return that we are alive */
+}
+
+/* MX mail check mailbox
+ * Accepts: MAIL stream
+ */
+
+void mx_check (MAILSTREAM *stream)
+{
+ if (mx_ping (stream)) MM_LOG ("Check completed",(long) NIL);
+}
+
+
+/* MX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long mx_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ MESSAGECACHE *elt;
+ unsigned long i = 1;
+ unsigned long n = 0;
+ unsigned long recent = stream->recent;
+ if (ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ mx_lockindex (stream)) { /* lock the index */
+ MM_CRITICAL (stream); /* go critical */
+ while (i <= stream->nmsgs) {/* for each message */
+ elt = mail_elt (stream,i);/* if deleted, need to trash it */
+ if (elt->deleted && (sequence ? elt->sequence : T)) {
+ sprintf (LOCAL->buf,"%s/%lu",stream->mailbox,elt->private.uid);
+ if (unlink (LOCAL->buf)) {/* try to delete the message */
+ sprintf (LOCAL->buf,"Expunge of message %lu failed, aborted: %s",i,
+ strerror (errno));
+ MM_LOG (LOCAL->buf,(long) NIL);
+ break;
+ }
+ /* note uncached */
+ LOCAL->cachedtexts -= ((elt->private.msg.header.text.data ?
+ elt->private.msg.header.text.size : 0) +
+ (elt->private.msg.text.text.data ?
+ elt->private.msg.text.text.size : 0));
+ mail_gc_msg (&elt->private.msg,GC_ENV | GC_TEXTS);
+ if(elt->recent)--recent;/* if recent, note one less recent message */
+ mail_expunged(stream,i);/* notify upper levels */
+ n++; /* count up one more expunged message */
+ }
+ else i++; /* otherwise try next message */
+ }
+ if (n) { /* output the news if any expunged */
+ sprintf (LOCAL->buf,"Expunged %lu messages",n);
+ MM_LOG (LOCAL->buf,(long) NIL);
+ }
+ else MM_LOG ("No messages deleted, so no update needed",(long) NIL);
+ MM_NOCRITICAL (stream); /* release critical */
+ mx_unlockindex (stream); /* finished with index */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ }
+ return ret;
+}
+
+/* MX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long mx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ FDDATA d;
+ STRING st;
+ MESSAGECACHE *elt;
+ MAILSTREAM *astream;
+ struct stat sbuf;
+ int fd;
+ unsigned long i,j,uid,uidv;
+ char *t,tmp[MAILTMPLEN];
+ long ret;
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ /* make sure valid mailbox */
+ if (!mx_valid (mailbox)) switch (errno) {
+ case NIL: /* no error in stat() */
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a MX-format mailbox: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ default: /* some stat() error */
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ }
+ /* copy the messages */
+ if (!(ret = ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))));
+ /* acquire stream to append to */
+ else if (!(astream = mail_open (NIL,mailbox,OP_SILENT))) {
+ MM_LOG ("Can't open copy mailbox",ERROR);
+ ret = NIL;
+ }
+ else {
+ MM_CRITICAL (stream); /* go critical */
+ if (!(ret = mx_lockindex (astream)))
+ MM_LOG ("Message copy failed: unable to lock index",ERROR);
+ else {
+
+ copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ for (i = 1,uid = uidv = 0; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ if (ret = ((fd = open (mx_fast_work (stream,elt),O_RDONLY,NIL))
+ >= 0)) {
+ fstat (fd,&sbuf); /* get size of message */
+ d.fd = fd; /* set up file descriptor */
+ d.pos = 0; /* start of file */
+ d.chunk = LOCAL->buf;
+ d.chunksize = CHUNKSIZE;
+ INIT (&st,fd_string,&d,sbuf.st_size);
+ /* init flag string */
+ tmp[0] = tmp[1] = '\0';
+ if (j = elt->user_flags) do
+ if (t = stream->user_flags[find_rightmost_bit (&j)])
+ strcat (strcat (tmp," "),t);
+ while (j);
+ if (elt->seen) strcat (tmp," \\Seen");
+ if (elt->deleted) strcat (tmp," \\Deleted");
+ if (elt->flagged) strcat (tmp," \\Flagged");
+ if (elt->answered) strcat (tmp," \\Answered");
+ if (elt->draft) strcat (tmp," \\Draft");
+ tmp[0] = '('; /* open list */
+ strcat (tmp,")"); /* close list */
+ if (ret = mx_append_msg (astream,tmp,elt,&st,dest)) {
+ /* add to source set if needed */
+ if (source) mail_append_set (source,mail_uid (stream,i));
+ /* delete if doing a move */
+ if (options & CP_MOVE) elt->deleted = T;
+ }
+ }
+ }
+ /* return sets if doing COPYUID */
+ if (cu && ret) (*cu) (stream,mailbox,astream->uid_validity,source,dest);
+ else { /* flush any sets we may have built */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ mx_unlockindex (astream); /* unlock index */
+ }
+ MM_NOCRITICAL (stream);
+ mail_close (astream); /* finished with append stream */
+ }
+ return ret; /* return success */
+}
+
+/* MX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ MESSAGECACHE elt;
+ MAILSTREAM *astream;
+ char *flags,*date,tmp[MAILTMPLEN];
+ STRING *message;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = user_flags (&mxproto);
+ /* N.B.: can't use LOCAL->buf for tmp */
+ /* make sure valid mailbox */
+ if (!mx_isvalid (mailbox,tmp)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!compare_cstring (mailbox,"INBOX")) mx_create (NIL,"INBOX");
+ else {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* falls through */
+ case 0: /* merely empty file? */
+ break;
+ case EINVAL:
+ sprintf (tmp,"Invalid MX-format mailbox name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+
+ /* get first message */
+ if (!MM_APPEND (af) (stream,data,&flags,&date,&message)) return NIL;
+ if (!(astream = mail_open (NIL,mailbox,OP_SILENT))) {
+ MM_LOG ("Can't open append mailbox",ERROR);
+ return NIL;
+ }
+ MM_CRITICAL (astream); /* go critical */
+ /* lock the index */
+ if (!(ret = mx_lockindex (astream)))
+ MM_LOG ("Message append failed: unable to lock index",ERROR);
+ else {
+ appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ do {
+ /* guard against zero-length */
+ if (!(ret = SIZE (message)))
+ MM_LOG ("Append of zero-length message",ERROR);
+ else if (date && !(ret = mail_parse_date (&elt,date))) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ }
+ else ret = mx_append_msg (astream,flags,date ? &elt : NIL,message,dst)&&
+ MM_APPEND (af) (stream,data,&flags,&date,&message);
+ } while (ret && message);
+ /* return sets if doing APPENDUID */
+ if (au && ret) (*au) (mailbox,astream->uid_validity,dst);
+ else mail_free_searchset (&dst);
+ mx_unlockindex (astream); /* unlock index */
+ }
+ MM_NOCRITICAL (astream); /* release critical */
+ mail_close (astream);
+ return ret;
+}
+
+/* MX mail append single message
+ * Accepts: MAIL stream
+ * flags for new message if non-NIL
+ * elt with source date if non-NIL
+ * stringstruct of message text
+ * searchset to place UID
+ * Returns: T if success, NIL if failure
+ */
+
+long mx_append_msg (MAILSTREAM *stream,char *flags,MESSAGECACHE *elt,
+ STRING *st,SEARCHSET *set)
+{
+ char tmp[MAILTMPLEN];
+ int fd;
+ unsigned long uf;
+ long f = mail_parse_flags (stream,flags,&uf);
+ /* make message file name */
+ sprintf (tmp,"%s/%lu",stream->mailbox,++stream->uid_last);
+ if ((fd = open (tmp,O_WRONLY|O_CREAT|O_EXCL,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0) {
+ sprintf (tmp,"Can't create append message: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ while (SIZE (st)) { /* copy the file */
+ if (st->cursize && (write (fd,st->curpos,st->cursize) < 0)) {
+ unlink (tmp); /* delete file */
+ close (fd); /* close the file */
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ SETPOS (st,GETPOS (st) + st->cursize);
+ }
+ close (fd); /* close the file */
+ if (elt) mx_setdate (tmp,elt);/* set file date */
+ /* swell the cache */
+ mail_exists (stream,++stream->nmsgs);
+ /* copy flags */
+ mail_append_set (set,(elt = mail_elt (stream,stream->nmsgs))->private.uid =
+ stream->uid_last);
+ if (f&fSEEN) elt->seen = T;
+ if (f&fDELETED) elt->deleted = T;
+ if (f&fFLAGGED) elt->flagged = T;
+ if (f&fANSWERED) elt->answered = T;
+ if (f&fDRAFT) elt->draft = T;
+ elt->user_flags |= uf;
+ return LONGT;
+}
+
+/* Internal routines */
+
+
+/* MX file name selection test
+ * Accepts: candidate directory entry
+ * Returns: T to use file name, NIL to skip it
+ */
+
+int mx_select (struct direct *name)
+{
+ char c;
+ char *s = name->d_name;
+ while (c = *s++) if (!isdigit (c)) return NIL;
+ return T;
+}
+
+
+/* MX file name comparision
+ * Accepts: first candidate directory entry
+ * second candidate directory entry
+ * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
+ */
+
+int mx_numsort (const void *d1,const void *d2)
+{
+ return atoi ((*(struct direct **) d1)->d_name) -
+ atoi ((*(struct direct **) d2)->d_name);
+}
+
+
+/* MX mail build file name
+ * Accepts: destination string
+ * source
+ * Returns: destination
+ */
+
+char *mx_file (char *dst,char *name)
+{
+ char *s;
+ /* empty string if mailboxfile fails */
+ if (!mailboxfile (dst,name)) *dst = '\0';
+ /* driver-selected INBOX */
+ else if (!*dst) mailboxfile (dst,"~/INBOX");
+ /* tie off unnecessary trailing / */
+ else if ((s = strrchr (dst,'/')) && !s[1]) *s = '\0';
+ return dst;
+}
+
+/* MX read and lock index
+ * Accepts: MAIL stream
+ * Returns: T if success, NIL if failure
+ */
+
+long mx_lockindex (MAILSTREAM *stream)
+{
+ unsigned long uf,sf,uid;
+ int k = 0;
+ unsigned long msgno = 1;
+ struct stat sbuf;
+ char *s,*t,*idx,tmp[2*MAILTMPLEN];
+ MESSAGECACHE *elt;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if ((LOCAL->fd < 0) && /* get index file, no-op if already have it */
+ (LOCAL->fd = open (strcat (strcpy (tmp,stream->mailbox),MXINDEXNAME),
+ O_RDWR|O_CREAT,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL)))
+ >= 0) {
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_EX); /* get exclusive lock */
+ (*bn) (BLOCK_NONE,NIL);
+ fstat (LOCAL->fd,&sbuf); /* get size of index */
+ /* slurp index */
+ read (LOCAL->fd,s = idx = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size);
+ idx[sbuf.st_size] = '\0'; /* tie off index */
+ /* parse index */
+ if (sbuf.st_size) while (s && *s) switch (*s) {
+ case 'V': /* UID validity record */
+ stream->uid_validity = strtoul (s+1,&s,16);
+ break;
+ case 'L': /* UID last record */
+ stream->uid_last = strtoul (s+1,&s,16);
+ break;
+ case 'K': /* keyword */
+ /* find end of keyword */
+ if (s = strchr (t = ++s,'\n')) {
+ *s++ = '\0'; /* tie off keyword */
+ /* copy keyword */
+ if ((k < NUSERFLAGS) && !stream->user_flags[k] &&
+ (strlen (t) <= MAXUSERFLAG)) stream->user_flags[k] = cpystr (t);
+ k++; /* one more keyword */
+ }
+ break;
+
+ case 'M': /* message status record */
+ uid = strtoul (s+1,&s,16);/* get UID for this message */
+ if (*s == ';') { /* get user flags */
+ uf = strtoul (s+1,&s,16);
+ if (*s == '.') { /* get system flags */
+ sf = strtoul (s+1,&s,16);
+ while ((msgno <= stream->nmsgs) && (mail_uid (stream,msgno) < uid))
+ msgno++; /* find message number for this UID */
+ if ((msgno <= stream->nmsgs) && (mail_uid (stream,msgno) == uid)) {
+ (elt = mail_elt (stream,msgno))->valid = T;
+ elt->user_flags=uf; /* set user and system flags in elt */
+ if (sf & fSEEN) elt->seen = T;
+ if (sf & fDELETED) elt->deleted = T;
+ if (sf & fFLAGGED) elt->flagged = T;
+ if (sf & fANSWERED) elt->answered = T;
+ if (sf & fDRAFT) elt->draft = T;
+ }
+ break;
+ }
+ }
+ default: /* bad news */
+ sprintf (tmp,"Error in index: %.80s",s);
+ MM_LOG (tmp,ERROR);
+ *s = NIL; /* ignore remainder of index */
+ }
+ else { /* new index */
+ stream->uid_validity = time (0);
+ user_flags (stream); /* init stream with default user flags */
+ }
+ fs_give ((void **) &idx); /* flush index */
+ }
+ return (LOCAL->fd >= 0) ? T : NIL;
+}
+
+/* MX write and unlock index
+ * Accepts: MAIL stream
+ */
+
+#define MXIXBUFLEN 2048
+
+void mx_unlockindex (MAILSTREAM *stream)
+{
+ unsigned long i,j;
+ off_t size = 0;
+ char *s,tmp[MXIXBUFLEN + 64];
+ MESSAGECACHE *elt;
+ if (LOCAL->fd >= 0) {
+ lseek (LOCAL->fd,0,L_SET); /* rewind file */
+ /* write header */
+ sprintf (s = tmp,"V%08lxL%08lx",stream->uid_validity,stream->uid_last);
+ for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; ++i)
+ sprintf (s += strlen (s),"K%s\n",stream->user_flags[i]);
+ /* write messages */
+ for (i = 1; i <= stream->nmsgs; i++) {
+ /* filled buffer? */
+ if (((s += strlen (s)) - tmp) > MXIXBUFLEN) {
+ write (LOCAL->fd,tmp,j = s - tmp);
+ size += j;
+ *(s = tmp) = '\0'; /* dump out and restart buffer */
+ }
+ elt = mail_elt (stream,i);
+ sprintf(s,"M%08lx;%08lx.%04x",elt->private.uid,elt->user_flags,(unsigned)
+ ((fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ }
+ /* write tail end of buffer */
+ if ((s += strlen (s)) != tmp) {
+ write (LOCAL->fd,tmp,j = s - tmp);
+ size += j;
+ }
+ ftruncate (LOCAL->fd,size);
+ flock (LOCAL->fd,LOCK_UN); /* unlock the index */
+ close (LOCAL->fd); /* finished with file */
+ LOCAL->fd = -1; /* no index now */
+ }
+}
+
+/* Set date for message
+ * Accepts: file name
+ * elt containing date
+ */
+
+void mx_setdate (char *file,MESSAGECACHE *elt)
+{
+ time_t tp[2];
+ tp[0] = time (0); /* atime is now */
+ tp[1] = mail_longdate (elt); /* modification time */
+ utime (file,tp); /* set the times */
+}
diff --git a/imap/src/osdep/amiga/news.c b/imap/src/osdep/amiga/news.c
new file mode 100644
index 00000000..4cf5bb70
--- /dev/null
+++ b/imap/src/osdep/amiga/news.c
@@ -0,0 +1,738 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: News routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 4 September 1991
+ * Last Edited: 30 January 2007
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "misc.h"
+#include "newsrc.h"
+#include "fdstring.h"
+
+
+/* news_load_message() flags */
+
+#define NLM_HEADER 0x1 /* load message text */
+#define NLM_TEXT 0x2 /* load message text */
+
+/* NEWS I/O stream local data */
+
+typedef struct news_local {
+ unsigned int dirty : 1; /* disk copy of .newsrc needs updating */
+ char *dir; /* spool directory name */
+ char *name; /* local mailbox name */
+ unsigned char buf[CHUNKSIZE]; /* scratch buffer */
+ unsigned long cachedtexts; /* total size of all cached texts */
+} NEWSLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((NEWSLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *news_valid (char *name);
+DRIVER *news_isvalid (char *name,char *mbx);
+void *news_parameters (long function,void *value);
+void news_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void news_list (MAILSTREAM *stream,char *ref,char *pat);
+void news_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long news_canonicalize (char *ref,char *pat,char *pattern);
+long news_subscribe (MAILSTREAM *stream,char *mailbox);
+long news_unsubscribe (MAILSTREAM *stream,char *mailbox);
+long news_create (MAILSTREAM *stream,char *mailbox);
+long news_delete (MAILSTREAM *stream,char *mailbox);
+long news_rename (MAILSTREAM *stream,char *old,char *newname);
+MAILSTREAM *news_open (MAILSTREAM *stream);
+int news_select (struct direct *name);
+int news_numsort (const void *d1,const void *d2);
+void news_close (MAILSTREAM *stream,long options);
+void news_fast (MAILSTREAM *stream,char *sequence,long flags);
+void news_flags (MAILSTREAM *stream,char *sequence,long flags);
+void news_load_message (MAILSTREAM *stream,unsigned long msgno,long flags);
+char *news_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long news_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void news_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long news_ping (MAILSTREAM *stream);
+void news_check (MAILSTREAM *stream);
+long news_expunge (MAILSTREAM *stream,char *sequence,long options);
+long news_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long news_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+/* News routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER newsdriver = {
+ "news", /* driver name */
+ /* driver flags */
+ DR_NEWS|DR_READONLY|DR_NOFAST|DR_NAMESPACE|DR_NONEWMAIL|DR_DIRFMT,
+ (DRIVER *) NIL, /* next driver */
+ news_valid, /* mailbox is valid for us */
+ news_parameters, /* manipulate parameters */
+ news_scan, /* scan mailboxes */
+ news_list, /* find mailboxes */
+ news_lsub, /* find subscribed mailboxes */
+ news_subscribe, /* subscribe to mailbox */
+ news_unsubscribe, /* unsubscribe from mailbox */
+ news_create, /* create mailbox */
+ news_delete, /* delete mailbox */
+ news_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ news_open, /* open mailbox */
+ news_close, /* close mailbox */
+ news_fast, /* fetch message "fast" attributes */
+ news_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ news_header, /* fetch message header */
+ news_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ news_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ news_ping, /* ping mailbox to see if still alive */
+ news_check, /* check for new messages */
+ news_expunge, /* expunge deleted messages */
+ news_copy, /* copy messages to another mailbox */
+ news_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM newsproto = {&newsdriver};
+
+/* News validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *news_valid (char *name)
+{
+ int fd;
+ char *s,*t,*u;
+ struct stat sbuf;
+ if ((name[0] == '#') && (name[1] == 'n') && (name[2] == 'e') &&
+ (name[3] == 'w') && (name[4] == 's') && (name[5] == '.') &&
+ !strchr (name,'/') &&
+ !stat ((char *) mail_parameters (NIL,GET_NEWSSPOOL,NIL),&sbuf) &&
+ ((fd = open ((char *) mail_parameters (NIL,GET_NEWSACTIVE,NIL),O_RDONLY,
+ NIL)) >= 0)) {
+ fstat (fd,&sbuf); /* get size of active file */
+ /* slurp in active file */
+ read (fd,t = s = (char *) fs_get (sbuf.st_size+1),sbuf.st_size);
+ s[sbuf.st_size] = '\0'; /* tie off file */
+ close (fd); /* flush file */
+ while (*t && (u = strchr (t,' '))) {
+ *u++ = '\0'; /* tie off at end of name */
+ if (!strcmp (name+6,t)) {
+ fs_give ((void **) &s); /* flush data */
+ return &newsdriver;
+ }
+ t = 1 + strchr (u,'\n'); /* next line */
+ }
+ fs_give ((void **) &s); /* flush data */
+ }
+ return NIL; /* return status */
+}
+
+/* News manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *news_parameters (long function,void *value)
+{
+ return (function == GET_NEWSRC) ? env_parameters (function,value) : NIL;
+}
+
+
+/* News scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void news_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ char tmp[MAILTMPLEN];
+ if (news_canonicalize (ref,pat,tmp))
+ mm_log ("Scan not valid for news mailboxes",ERROR);
+}
+
+/* News find list of newsgroups
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void news_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ int fd;
+ int i;
+ char *s,*t,*u,*r,pattern[MAILTMPLEN],name[MAILTMPLEN];
+ struct stat sbuf;
+ if (!pat || !*pat) { /* empty pattern? */
+ if (news_canonicalize (ref,"*",pattern)) {
+ /* tie off name at root */
+ if (s = strchr (pattern,'.')) *++s = '\0';
+ else pattern[0] = '\0';
+ mm_list (stream,'.',pattern,LATT_NOSELECT);
+ }
+ }
+ else if (news_canonicalize (ref,pat,pattern) &&
+ !stat ((char *) mail_parameters (NIL,GET_NEWSSPOOL,NIL),&sbuf) &&
+ ((fd = open ((char *) mail_parameters (NIL,GET_NEWSACTIVE,NIL),
+ O_RDONLY,NIL)) >= 0)) {
+ fstat (fd,&sbuf); /* get file size and read data */
+ read (fd,s = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size);
+ close (fd); /* close file */
+ s[sbuf.st_size] = '\0'; /* tie off string */
+ strcpy (name,"#news."); /* write initial prefix */
+ i = strlen (pattern); /* length of pattern */
+ if (pattern[--i] != '%') i = 0;
+ if (t = strtok_r (s,"\n",&r)) do if (u = strchr (t,' ')) {
+ *u = '\0'; /* tie off at end of name */
+ strcpy (name + 6,t); /* make full form of name */
+ if (pmatch_full (name,pattern,'.')) mm_list (stream,'.',name,NIL);
+ else if (i && (u = strchr (name + i,'.'))) {
+ *u = '\0'; /* tie off at delimiter, see if matches */
+ if (pmatch_full (name,pattern,'.'))
+ mm_list (stream,'.',name,LATT_NOSELECT);
+ }
+ } while (t = strtok_r (NIL,"\n",&r));
+ fs_give ((void **) &s);
+ }
+}
+
+/* News find list of subscribed newsgroups
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void news_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ char pattern[MAILTMPLEN];
+ /* return data from newsrc */
+ if (news_canonicalize (ref,pat,pattern)) newsrc_lsub (stream,pattern);
+}
+
+
+/* News canonicalize newsgroup name
+ * Accepts: reference
+ * pattern
+ * returned single pattern
+ * Returns: T on success, NIL on failure
+ */
+
+long news_canonicalize (char *ref,char *pat,char *pattern)
+{
+ unsigned long i;
+ char *s;
+ if (ref && *ref) { /* have a reference */
+ strcpy (pattern,ref); /* copy reference to pattern */
+ /* # overrides mailbox field in reference */
+ if (*pat == '#') strcpy (pattern,pat);
+ /* pattern starts, reference ends, with . */
+ else if ((*pat == '.') && (pattern[strlen (pattern) - 1] == '.'))
+ strcat (pattern,pat + 1); /* append, omitting one of the period */
+ else strcat (pattern,pat); /* anything else is just appended */
+ }
+ else strcpy (pattern,pat); /* just have basic name */
+ if ((pattern[0] == '#') && (pattern[1] == 'n') && (pattern[2] == 'e') &&
+ (pattern[3] == 'w') && (pattern[4] == 's') && (pattern[5] == '.') &&
+ !strchr (pattern,'/')) { /* count wildcards */
+ for (i = 0, s = pattern; *s; *s++) if ((*s == '*') || (*s == '%')) ++i;
+ /* success if not too many */
+ if (i <= MAXWILDCARDS) return LONGT;
+ MM_LOG ("Excessive wildcards in LIST/LSUB",ERROR);
+ }
+ return NIL;
+}
+
+/* News subscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to add to subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long news_subscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return news_valid (mailbox) ? newsrc_update (stream,mailbox+6,':') : NIL;
+}
+
+
+/* NEWS unsubscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to delete from subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long news_unsubscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return news_valid (mailbox) ? newsrc_update (stream,mailbox+6,'!') : NIL;
+}
+
+/* News create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long news_create (MAILSTREAM *stream,char *mailbox)
+{
+ return NIL; /* never valid for News */
+}
+
+
+/* News delete mailbox
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long news_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return NIL; /* never valid for News */
+}
+
+
+/* News rename mailbox
+ * Accepts: mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long news_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ return NIL; /* never valid for News */
+}
+
+/* News open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *news_open (MAILSTREAM *stream)
+{
+ long i,nmsgs;
+ char *s,tmp[MAILTMPLEN];
+ struct direct **names = NIL;
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &newsproto;
+ if (stream->local) fatal ("news recycle stream");
+ /* build directory name */
+ sprintf (s = tmp,"%s/%s",(char *) mail_parameters (NIL,GET_NEWSSPOOL,NIL),
+ stream->mailbox + 6);
+ while (s = strchr (s,'.')) *s = '/';
+ /* scan directory */
+ if ((nmsgs = scandir (tmp,&names,news_select,news_numsort)) >= 0) {
+ mail_exists (stream,nmsgs); /* notify upper level that messages exist */
+ stream->local = fs_get (sizeof (NEWSLOCAL));
+ LOCAL->dirty = NIL; /* no update to .newsrc needed yet */
+ LOCAL->dir = cpystr (tmp); /* copy directory name for later */
+ LOCAL->name = cpystr (stream->mailbox + 6);
+ for (i = 0; i < nmsgs; ++i) {
+ stream->uid_last = mail_elt (stream,i+1)->private.uid =
+ atoi (names[i]->d_name);
+ fs_give ((void **) &names[i]);
+ }
+ s = (void *) names; /* stupid language */
+ fs_give ((void **) &s); /* free directory */
+ LOCAL->cachedtexts = 0; /* no cached texts */
+ stream->sequence++; /* bump sequence number */
+ stream->rdonly = stream->perm_deleted = T;
+ /* UIDs are always valid */
+ stream->uid_validity = 0xbeefface;
+ /* read .newsrc entries */
+ mail_recent (stream,newsrc_read (LOCAL->name,stream));
+ /* notify if empty newsgroup */
+ if (!(stream->nmsgs || stream->silent)) {
+ sprintf (tmp,"Newsgroup %s is empty",LOCAL->name);
+ mm_log (tmp,WARN);
+ }
+ }
+ else mm_log ("Unable to scan newsgroup spool directory",ERROR);
+ return LOCAL ? stream : NIL; /* if stream is alive, return to caller */
+}
+
+/* News file name selection test
+ * Accepts: candidate directory entry
+ * Returns: T to use file name, NIL to skip it
+ */
+
+int news_select (struct direct *name)
+{
+ char c;
+ char *s = name->d_name;
+ while (c = *s++) if (!isdigit (c)) return NIL;
+ return T;
+}
+
+
+/* News file name comparision
+ * Accepts: first candidate directory entry
+ * second candidate directory entry
+ * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
+ */
+
+int news_numsort (const void *d1,const void *d2)
+{
+ return atoi ((*(struct direct **) d1)->d_name) -
+ atoi ((*(struct direct **) d2)->d_name);
+}
+
+
+/* News close
+ * Accepts: MAIL stream
+ * option flags
+ */
+
+void news_close (MAILSTREAM *stream,long options)
+{
+ if (LOCAL) { /* only if a file is open */
+ news_check (stream); /* dump final checkpoint */
+ if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
+ if (LOCAL->name) fs_give ((void **) &LOCAL->name);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* News fetch fast information
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ */
+
+void news_fast (MAILSTREAM *stream,char *sequence,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned long i;
+ /* set up metadata for all messages */
+ if (stream && LOCAL && ((flags & FT_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence &&
+ !(elt->day && elt->rfc822_size)) news_load_message (stream,i,NIL);
+}
+
+
+/* News fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ */
+
+void news_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ unsigned long i;
+ if ((flags & FT_UID) ? /* validate all elts */
+ mail_uid_sequence (stream,sequence) : mail_sequence (stream,sequence))
+ for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->valid = T;
+}
+
+/* News load message into cache
+ * Accepts: MAIL stream
+ * message #
+ * option flags
+ */
+
+void news_load_message (MAILSTREAM *stream,unsigned long msgno,long flags)
+{
+ unsigned long i,j,nlseen;
+ int fd;
+ unsigned char c,*t;
+ struct stat sbuf;
+ MESSAGECACHE *elt;
+ FDDATA d;
+ STRING bs;
+ elt = mail_elt (stream,msgno);/* get elt */
+ /* build message file name */
+ sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
+ /* anything we need not currently cached? */
+ if ((!elt->day || !elt->rfc822_size ||
+ ((flags & NLM_HEADER) && !elt->private.msg.header.text.data) ||
+ ((flags & NLM_TEXT) && !elt->private.msg.text.text.data)) &&
+ ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0)) {
+ fstat (fd,&sbuf); /* get file metadata */
+ d.fd = fd; /* set up file descriptor */
+ d.pos = 0; /* start of file */
+ d.chunk = LOCAL->buf;
+ d.chunksize = CHUNKSIZE;
+ INIT (&bs,fd_string,&d,sbuf.st_size);
+ if (!elt->day) { /* set internaldate to file date */
+ struct tm *tm = gmtime (&sbuf.st_mtime);
+ elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
+ elt->year = tm->tm_year + 1900 - BASEYEAR;
+ elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
+ elt->seconds = tm->tm_sec;
+ elt->zhours = 0; elt->zminutes = 0;
+ }
+
+ if (!elt->rfc822_size) { /* know message size yet? */
+ for (i = 0, j = SIZE (&bs), nlseen = 0; j--; ) switch (SNX (&bs)) {
+ case '\015': /* unlikely carriage return */
+ if (!j || (CHR (&bs) != '\012')) {
+ i++; /* ugh, raw CR */
+ nlseen = NIL;
+ break;
+ }
+ SNX (&bs); /* eat the line feed, drop in */
+ case '\012': /* line feed? */
+ i += 2; /* count a CRLF */
+ /* header size known yet? */
+ if (!elt->private.msg.header.text.size && nlseen) {
+ /* note position in file */
+ elt->private.special.text.size = GETPOS (&bs);
+ /* and CRLF-adjusted size */
+ elt->private.msg.header.text.size = i;
+ }
+ nlseen = T; /* note newline seen */
+ break;
+ default: /* ordinary chararacter */
+ i++;
+ nlseen = NIL;
+ break;
+ }
+ SETPOS (&bs,0); /* restore old position */
+ elt->rfc822_size = i; /* note that we have size now */
+ /* header is entire message if no delimiter */
+ if (!elt->private.msg.header.text.size)
+ elt->private.msg.header.text.size = elt->rfc822_size;
+ /* text is remainder of message */
+ elt->private.msg.text.text.size =
+ elt->rfc822_size - elt->private.msg.header.text.size;
+ }
+
+ /* need to load cache with message data? */
+ if (((flags & NLM_HEADER) && !elt->private.msg.header.text.data) ||
+ ((flags & NLM_TEXT) && !elt->private.msg.text.text.data)) {
+ /* purge cache if too big */
+ if (LOCAL->cachedtexts > max (stream->nmsgs * 4096,2097152)) {
+ /* just can't keep that much */
+ mail_gc (stream,GC_TEXTS);
+ LOCAL->cachedtexts = 0;
+ }
+ if ((flags & NLM_HEADER) && !elt->private.msg.header.text.data) {
+ t = elt->private.msg.header.text.data =
+ (unsigned char *) fs_get (elt->private.msg.header.text.size + 1);
+ LOCAL->cachedtexts += elt->private.msg.header.text.size;
+ /* read in message header */
+ for (i = 0; i <= elt->private.msg.header.text.size; i++)
+ switch (c = SNX (&bs)) {
+ case '\015': /* unlikely carriage return */
+ *t++ = c;
+ if ((CHR (&bs) == '\012')) *t++ = SNX (&bs);
+ break;
+ case '\012': /* line feed? */
+ *t++ = '\015';
+ default:
+ *t++ = c;
+ break;
+ }
+ *t = '\0'; /* tie off string */
+ }
+ if ((flags & NLM_TEXT) && !elt->private.msg.text.text.data) {
+ t = elt->private.msg.text.text.data =
+ (unsigned char *) fs_get (elt->private.msg.text.text.size + 1);
+ SETPOS (&bs,elt->private.msg.header.text.size);
+ LOCAL->cachedtexts += elt->private.msg.text.text.size;
+ /* read in message text */
+ for (i = 0; i <= elt->private.msg.text.text.size; i++)
+ switch (c = SNX (&bs)) {
+ case '\015': /* unlikely carriage return */
+ *t++ = c;
+ if ((CHR (&bs) == '\012')) *t++ = SNX (&bs);
+ break;
+ case '\012': /* line feed? */
+ *t++ = '\015';
+ default:
+ *t++ = c;
+ break;
+ }
+ *t = '\0'; /* tie off string */
+ }
+ }
+ close (fd); /* flush message file */
+ }
+}
+
+/* News fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *news_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ MESSAGECACHE *elt;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get elt */
+ if (!elt->private.msg.header.text.data)
+ news_load_message (stream,msgno,NLM_HEADER);
+ *length = elt->private.msg.header.text.size;
+ return (char *) elt->private.msg.header.text.data;
+}
+
+
+/* News fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL on failure
+ */
+
+long news_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);/* get elt */
+ /* snarf message if don't have it yet */
+ if (!elt->private.msg.text.text.data) {
+ news_load_message (stream,msgno,NLM_TEXT);
+ if (!elt->private.msg.text.text.data) return NIL;
+ }
+ if (!(flags & FT_PEEK)) { /* mark as seen */
+ mail_elt (stream,msgno)->seen = T;
+ mm_flags (stream,msgno);
+ }
+ INIT (bs,mail_string,elt->private.msg.text.text.data,
+ elt->private.msg.text.text.size);
+ return T;
+}
+
+/* News per-message modify flag
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void news_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ if (!LOCAL->dirty) { /* only bother checking if not dirty yet */
+ if (elt->valid) { /* if done, see if deleted changed */
+ if (elt->sequence != elt->deleted) LOCAL->dirty = T;
+ elt->sequence = T; /* leave the sequence set */
+ }
+ /* note current setting of deleted flag */
+ else elt->sequence = elt->deleted;
+ }
+}
+
+
+/* News ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long news_ping (MAILSTREAM *stream)
+{
+ return T; /* always alive */
+}
+
+
+/* News check mailbox
+ * Accepts: MAIL stream
+ */
+
+void news_check (MAILSTREAM *stream)
+{
+ /* never do if no updates */
+ if (LOCAL->dirty) newsrc_write (LOCAL->name,stream);
+ LOCAL->dirty = NIL;
+}
+
+
+/* News expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T if success, NIL if failure
+ */
+
+long news_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
+ return LONGT;
+}
+
+/* News copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * option flags
+ * Returns: T if copy successful, else NIL
+ */
+
+long news_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ mm_log ("Copy not valid for News",ERROR);
+ return NIL;
+}
+
+
+/* News append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback function
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long news_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ mm_log ("Append not valid for news",ERROR);
+ return NIL;
+}
diff --git a/imap/src/osdep/amiga/nl_ami.c b/imap/src/osdep/amiga/nl_ami.c
new file mode 100644
index 00000000..b2d5616a
--- /dev/null
+++ b/imap/src/osdep/amiga/nl_ami.c
@@ -0,0 +1,92 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX/VMS newline routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Copy string with CRLF newlines
+ * Accepts: destination string
+ * pointer to size of destination string buffer
+ * source string
+ * length of source string
+ * Returns: length of copied string
+ */
+
+unsigned long strcrlfcpy (unsigned char **dst,unsigned long *dstl,
+ unsigned char *src,unsigned long srcl)
+{
+ long i = srcl * 2,j;
+ unsigned char c,*d = src;
+ if (*dst) { /* candidate destination provided? */
+ /* count NLs if doesn't fit worst-case */
+ if (i > *dstl) for (i = j = srcl; j; --j) if (*d++ == '\012') i++;
+ /* still too small, must reset destination */
+ if (i > *dstl) fs_give ((void **) dst);
+ }
+ /* make a new buffer if needed */
+ if (!*dst) *dst = (char *) fs_get ((*dstl = i) + 1);
+ d = *dst; /* destination string */
+ if (srcl) do { /* main copy loop */
+ if ((c = *src++) < '\016') {
+ /* prepend CR to LF */
+ if (c == '\012') *d++ = '\015';
+ /* unlikely CR */
+ else if ((c == '\015') && (srcl > 1) && (*src == '\012')) {
+ *d++ = c; /* copy the CR */
+ c = *src++; /* grab the LF */
+ --srcl; /* adjust the count */
+ }
+ }
+ *d++ = c; /* copy character */
+ } while (--srcl);
+ *d = '\0'; /* tie off destination */
+ return d - *dst; /* return length */
+}
+
+/* Length of string after strcrlfcpy applied
+ * Accepts: source string
+ * Returns: length of string
+ */
+
+unsigned long strcrlflen (STRING *s)
+{
+ unsigned long pos = GETPOS (s);
+ unsigned long i = SIZE (s);
+ unsigned long j = i;
+ while (j--) switch (SNX (s)) {/* search for newlines */
+ case '\015': /* unlikely carriage return */
+ if (j && (CHR (s) == '\012')) {
+ SNX (s); /* eat the line feed */
+ j--;
+ }
+ break;
+ case '\012': /* line feed? */
+ i++;
+ default: /* ordinary chararacter */
+ break;
+ }
+ SETPOS (s,pos); /* restore old position */
+ return i;
+}
diff --git a/imap/src/osdep/amiga/os_ami.c b/imap/src/osdep/amiga/os_ami.c
new file mode 100644
index 00000000..1ba3ca5f
--- /dev/null
+++ b/imap/src/osdep/amiga/os_ami.c
@@ -0,0 +1,80 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Amiga version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#define PINE /* to get full DIR description in <dirent.h> */
+#include "tcp_ami.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+#define DIR_SIZE(d) d->d_reclen /* for scandir.c */
+
+#include "fs_ami.c"
+#include "ftl_ami.c"
+#include "nl_ami.c"
+#include "env_ami.c"
+#include "tcp_ami.c"
+#include "log_std.c"
+#include "gr_waitp.c"
+#include "tz_bsd.c"
+#include "scandir.c"
+#include "gethstid.c"
+
+#undef utime
+
+/* Amiga has its own wierd utime() with an incompatible struct utimbuf that
+ * does not match with the traditional time_t [2].
+ */
+
+/* Portable utime() that takes it args like real Unix systems
+ * Accepts: file path
+ * traditional utime() argument
+ * Returns: utime() results
+ */
+
+int portable_utime (char *file,time_t timep[2])
+{
+ struct utimbuf times;
+ times.actime = timep[0]; /* copy the portable values */
+ times.modtime = timep[1];
+ return utime (file,&times); /* now call Amiga's routine */
+}
diff --git a/imap/src/osdep/amiga/os_ami.h b/imap/src/osdep/amiga/os_ami.h
new file mode 100644
index 00000000..293005ca
--- /dev/null
+++ b/imap/src/osdep/amiga/os_ami.h
@@ -0,0 +1,54 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Amiga version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 15 September 2006
+ */
+
+#include <proto/socket.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+/* Different names, equivalent things in BSD and SysV */
+
+#define direct dirent
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+#include "env_ami.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+
+long gethostid (void);
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (void *d1,void *d2);
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar);
+int alphasort (void *d1,void *d2);
diff --git a/imap/src/osdep/amiga/phile.c b/imap/src/osdep/amiga/phile.c
new file mode 100644
index 00000000..ce72d0a9
--- /dev/null
+++ b/imap/src/osdep/amiga/phile.c
@@ -0,0 +1,553 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: File routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 25 August 1993
+ * Last Edited: 9 May 2006
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <signal.h>
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "rfc822.h"
+#include "misc.h"
+#include "dummy.h"
+
+/* Types returned from phile_type() */
+
+#define PTYPEBINARY 0 /* binary data */
+#define PTYPETEXT 1 /* textual data */
+#define PTYPECRTEXT 2 /* textual data with CR */
+#define PTYPE8 4 /* textual 8bit data */
+#define PTYPEISO2022JP 8 /* textual Japanese */
+#define PTYPEISO2022KR 16 /* textual Korean */
+#define PTYPEISO2022CN 32 /* textual Chinese */
+
+
+/* PHILE I/O stream local data */
+
+typedef struct phile_local {
+ ENVELOPE *env; /* file envelope */
+ BODY *body; /* file body */
+ char tmp[MAILTMPLEN]; /* temporary buffer */
+} PHILELOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((PHILELOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *phile_valid (char *name);
+int phile_isvalid (char *name,char *tmp);
+void *phile_parameters (long function,void *value);
+void phile_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void phile_list (MAILSTREAM *stream,char *ref,char *pat);
+void phile_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long phile_create (MAILSTREAM *stream,char *mailbox);
+long phile_delete (MAILSTREAM *stream,char *mailbox);
+long phile_rename (MAILSTREAM *stream,char *old,char *newname);
+long phile_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *phile_open (MAILSTREAM *stream);
+int phile_type (unsigned char *s,unsigned long i,unsigned long *j);
+void phile_close (MAILSTREAM *stream,long options);
+ENVELOPE *phile_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body,
+ long flags);
+char *phile_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long phile_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+long phile_ping (MAILSTREAM *stream);
+void phile_check (MAILSTREAM *stream);
+long phile_expunge (MAILSTREAM *stream,char *sequence,long options);
+long phile_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long phile_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+/* File routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER philedriver = {
+ "phile", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_READONLY|DR_NOSTICKY,
+ (DRIVER *) NIL, /* next driver */
+ phile_valid, /* mailbox is valid for us */
+ phile_parameters, /* manipulate parameters */
+ phile_scan, /* scan mailboxes */
+ phile_list, /* list mailboxes */
+ phile_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ dummy_delete, /* delete mailbox */
+ dummy_rename, /* rename mailbox */
+ phile_status, /* status of mailbox */
+ phile_open, /* open mailbox */
+ phile_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ phile_structure, /* fetch message envelopes */
+ phile_header, /* fetch message header only */
+ phile_text, /* fetch message body only */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ phile_ping, /* ping mailbox to see if still alive */
+ phile_check, /* check for new messages */
+ phile_expunge, /* expunge deleted messages */
+ phile_copy, /* copy messages to another mailbox */
+ phile_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM phileproto = {&philedriver};
+
+/* File validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *phile_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return phile_isvalid (name,tmp) ? &philedriver : NIL;
+}
+
+
+/* File test for valid mailbox
+ * Accepts: mailbox name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int phile_isvalid (char *name,char *tmp)
+{
+ struct stat sbuf;
+ char *s;
+ /* INBOX never accepted, any other name is */
+ return ((s = mailboxfile (tmp,name)) && *s && !stat (s,&sbuf) &&
+ !(sbuf.st_mode & S_IFDIR) &&
+ /* only allow empty files if no empty proto
+ or if #ftp */
+ (sbuf.st_size || !default_proto (T) ||
+ ((*name == '#') && ((name[1] == 'f') || (name[1] == 'F')) &&
+ ((name[2] == 't') || (name[2] == 'T')) &&
+ ((name[3] == 'p') || (name[3] == 'P')) && (name[4] == '/'))));
+}
+
+/* File manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *phile_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* File mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void phile_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* File list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void phile_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* File list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void phile_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+
+/* File status
+ * Accepts: mail stream
+ * mailbox name
+ * status flags
+ * Returns: T on success, NIL on failure
+ */
+
+long phile_status (MAILSTREAM *stream,char *mbx,long flags)
+{
+ char *s,tmp[MAILTMPLEN];
+ MAILSTATUS status;
+ struct stat sbuf;
+ long ret = NIL;
+ if ((s = mailboxfile (tmp,mbx)) && *s && !stat (s,&sbuf)) {
+ status.flags = flags; /* return status values */
+ status.unseen = (stream && mail_elt (stream,1)->seen) ? 0 : 1;
+ status.messages = status.recent = status.uidnext = 1;
+ status.uidvalidity = sbuf.st_mtime;
+ /* pass status to main program */
+ mm_status (stream,mbx,&status);
+ ret = LONGT; /* success */
+ }
+ return ret;
+}
+
+/* File open
+ * Accepts: Stream to open
+ * Returns: Stream on success, NIL on failure
+ */
+
+MAILSTREAM *phile_open (MAILSTREAM *stream)
+{
+ int i,k,fd;
+ unsigned long j,m;
+ char *s,tmp[MAILTMPLEN];
+ struct passwd *pw;
+ struct stat sbuf;
+ struct tm *t;
+ MESSAGECACHE *elt;
+ SIZEDTEXT *buf;
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &phileproto;
+ if (stream->local) fatal ("phile recycle stream");
+ /* open associated file */
+ if (!mailboxfile (tmp,stream->mailbox) || !tmp[0] || stat (tmp,&sbuf) ||
+ (fd = open (tmp,O_RDONLY,NIL)) < 0) {
+ sprintf (tmp,"Unable to open file %s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ stream->local = fs_get (sizeof (PHILELOCAL));
+ mail_exists (stream,1); /* make sure upper level knows */
+ mail_recent (stream,1);
+ elt = mail_elt (stream,1); /* instantiate cache element */
+ elt->valid = elt->recent = T; /* mark valid flags */
+ stream->sequence++; /* bump sequence number */
+ stream->rdonly = T; /* make sure upper level knows readonly */
+ /* instantiate a new envelope and body */
+ LOCAL->env = mail_newenvelope ();
+ LOCAL->body = mail_newbody ();
+
+ t = gmtime (&sbuf.st_mtime); /* get UTC time and Julian day */
+ i = t->tm_hour * 60 + t->tm_min;
+ k = t->tm_yday;
+ t = localtime(&sbuf.st_mtime);/* get local time */
+ /* calculate time delta */
+ i = t->tm_hour * 60 + t->tm_min - i;
+ if (k = t->tm_yday - k) i += ((k < 0) == (abs (k) == 1)) ? -24*60 : 24*60;
+ k = abs (i); /* time from UTC either way */
+ elt->hours = t->tm_hour; elt->minutes = t->tm_min; elt->seconds = t->tm_sec;
+ elt->day = t->tm_mday; elt->month = t->tm_mon + 1;
+ elt->year = t->tm_year - (BASEYEAR - 1900);
+ elt->zoccident = (k == i) ? 0 : 1;
+ elt->zhours = k/60;
+ elt->zminutes = k % 60;
+ sprintf (tmp,"%s, %d %s %d %02d:%02d:%02d %c%02d%02d",
+ days[t->tm_wday],t->tm_mday,months[t->tm_mon],t->tm_year+1900,
+ t->tm_hour,t->tm_min,t->tm_sec,elt->zoccident ? '-' : '+',
+ elt->zhours,elt->zminutes);
+ /* set up Date field */
+ LOCAL->env->date = cpystr (tmp);
+
+ /* fill in From field from file owner */
+ LOCAL->env->from = mail_newaddr ();
+ if (pw = getpwuid (sbuf.st_uid)) strcpy (tmp,pw->pw_name);
+ else sprintf (tmp,"User-Number-%ld",(long) sbuf.st_uid);
+ LOCAL->env->from->mailbox = cpystr (tmp);
+ LOCAL->env->from->host = cpystr (mylocalhost ());
+ /* set subject to be mailbox name */
+ LOCAL->env->subject = cpystr (stream->mailbox);
+ /* slurp the data */
+ (buf = &elt->private.special.text)->size = sbuf.st_size;
+ read (fd,buf->data = (unsigned char *) fs_get (buf->size + 1),buf->size);
+ buf->data[buf->size] = '\0';
+ close (fd); /* close the file */
+ /* analyze data type */
+ if (i = phile_type (buf->data,buf->size,&j)) {
+ LOCAL->body->type = TYPETEXT;
+ LOCAL->body->subtype = cpystr ("PLAIN");
+ if (!(i & PTYPECRTEXT)) { /* change Internet newline format as needed */
+ s = (char *) buf->data; /* make copy of UNIX-format string */
+ buf->data = NIL; /* zap the buffer */
+ buf->size = strcrlfcpy (&buf->data,&m,s,buf->size);
+ fs_give ((void **) &s); /* flush original UNIX-format string */
+ }
+ LOCAL->body->parameter = mail_newbody_parameter ();
+ LOCAL->body->parameter->attribute = cpystr ("charset");
+ LOCAL->body->parameter->value =
+ cpystr ((i & PTYPEISO2022JP) ? "ISO-2022-JP" :
+ (i & PTYPEISO2022KR) ? "ISO-2022-KR" :
+ (i & PTYPEISO2022CN) ? "ISO-2022-CN" :
+ (i & PTYPE8) ? "X-UNKNOWN" : "US-ASCII");
+ LOCAL->body->encoding = (i & PTYPE8) ? ENC8BIT : ENC7BIT;
+ LOCAL->body->size.lines = j;
+ }
+ else { /* binary data */
+ LOCAL->body->type = TYPEAPPLICATION;
+ LOCAL->body->subtype = cpystr ("OCTET-STREAM");
+ LOCAL->body->parameter = mail_newbody_parameter ();
+ LOCAL->body->parameter->attribute = cpystr ("name");
+ LOCAL->body->parameter->value =
+ cpystr ((s = (strrchr (stream->mailbox,'/'))) ? s+1 : stream->mailbox);
+ LOCAL->body->encoding = ENCBASE64;
+ buf->data = rfc822_binary (s = (char *) buf->data,buf->size,&buf->size);
+ fs_give ((void **) &s); /* flush originary binary contents */
+ }
+ phile_header (stream,1,&j,NIL);
+ LOCAL->body->size.bytes = LOCAL->body->contents.text.size = buf->size;
+ elt->rfc822_size = j + buf->size;
+ /* only one message ever... */
+ stream->uid_validity = sbuf.st_mtime;
+ stream->uid_last = elt->private.uid = 1;
+ return stream; /* return stream alive to caller */
+}
+
+/* File determine data type
+ * Accepts: data to examine
+ * size of data
+ * pointer to line count return
+ * Returns: PTYPE mask of data type
+ */
+
+int phile_type (unsigned char *s,unsigned long i,unsigned long *j)
+{
+ int ret = PTYPETEXT;
+ char *charvec = "bbbbbbbaaalaacaabbbbbbbbbbbebbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+ *j = 0; /* no lines */
+ /* check type of every character */
+ while (i--) switch (charvec[*s++]) {
+ case 'A':
+ ret |= PTYPE8; /* 8bit character */
+ break;
+ case 'a':
+ break; /* ASCII character */
+ case 'b':
+ return PTYPEBINARY; /* binary byte seen, stop immediately */
+ case 'c':
+ ret |= PTYPECRTEXT; /* CR indicates Internet text */
+ break;
+ case 'e': /* ESC */
+ if (*s == '$') { /* ISO-2022 sequence? */
+ switch (s[1]) {
+ case 'B': case '@': ret |= PTYPEISO2022JP; break;
+ case ')':
+ switch (s[2]) {
+ case 'A': case 'E': case 'G': ret |= PTYPEISO2022CN; break;
+ case 'C': ret |= PTYPEISO2022KR; break;
+ }
+ case '*':
+ switch (s[2]) {
+ case 'H': ret |= PTYPEISO2022CN; break;
+ }
+ case '+':
+ switch (s[2]) {
+ case 'I': case 'J': case 'K': case 'L': case 'M':
+ ret |= PTYPEISO2022CN; break;
+ }
+ }
+ }
+ break;
+ case 'l': /* newline */
+ (*j)++;
+ break;
+ }
+ return ret; /* return type of data */
+}
+
+/* File close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void phile_close (MAILSTREAM *stream,long options)
+{
+ if (LOCAL) { /* only if a file is open */
+ fs_give ((void **) &mail_elt (stream,1)->private.special.text.data);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* File fetch structure
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to return body
+ * option flags
+ * Returns: envelope of this message, body returned in body value
+ *
+ * Fetches the "fast" information as well
+ */
+
+ENVELOPE *phile_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body,
+ long flags)
+{
+ if (body) *body = LOCAL->body;
+ return LOCAL->env; /* return the envelope */
+}
+
+
+/* File fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *phile_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ rfc822_header (LOCAL->tmp,LOCAL->env,LOCAL->body);
+ *length = strlen (LOCAL->tmp);
+ return LOCAL->tmp;
+}
+
+
+/* File fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T, always
+ */
+
+long phile_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ SIZEDTEXT *buf = &mail_elt (stream,msgno)->private.special.text;
+ if (!(flags &FT_PEEK)) { /* mark message as seen */
+ mail_elt (stream,msgno)->seen = T;
+ mm_flags (stream,msgno);
+ }
+ INIT (bs,mail_string,buf->data,buf->size);
+ return T;
+}
+
+/* File ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+long phile_ping (MAILSTREAM *stream)
+{
+ return T;
+}
+
+/* File check mailbox
+ * Accepts: MAIL stream
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+void phile_check (MAILSTREAM *stream)
+{
+ mm_log ("Check completed",NIL);
+}
+
+/* File expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T if success, NIL if failure
+ */
+
+long phile_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
+ return LONGT;
+}
+
+/* File copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long phile_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ char tmp[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (tmp,"Can't copy - file \"%s\" is not in valid mailbox format",
+ stream->mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+}
+
+
+/* File append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback function
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long phile_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ char tmp[MAILTMPLEN],file[MAILTMPLEN];
+ char *s = mailboxfile (file,mailbox);
+ if (s && *s)
+ sprintf (tmp,"Can't append - not in valid mailbox format: %.80s",s);
+ else sprintf (tmp,"Can't append - invalid name: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+}
diff --git a/imap/src/osdep/amiga/pmatch.c b/imap/src/osdep/amiga/pmatch.c
new file mode 100644
index 00000000..a7539e50
--- /dev/null
+++ b/imap/src/osdep/amiga/pmatch.c
@@ -0,0 +1,89 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: IMAP Wildcard Matching Routines (case-dependent)
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 June 2000
+ * Last Edited: 30 August 2006
+ */
+
+/* Wildcard pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if pattern matches base, else NIL
+ */
+
+long pmatch_full (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ /* % at end, OK if no inferiors */
+ if (!pat[1]) return (delim && strchr (s,delim)) ? NIL : T;
+ /* scan remainder of string until delimiter */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while ((*s != delim) && *s++);
+ break;
+ case '*': /* match 0 or more characters */
+ if (!pat[1]) return T; /* * at end, unconditional match */
+ /* scan remainder of string */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while (*s++);
+ break;
+ case '\0': /* end of pattern */
+ return *s ? NIL : T; /* success if also end of base */
+ default: /* match this character */
+ return (*pat == *s) ? pmatch_full (s+1,pat+1,delim) : NIL;
+ }
+ return NIL;
+}
+
+/* Directory pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if base is a matching directory of pattern, else NIL
+ */
+
+long dmatch (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ if (!*s) return T; /* end of base means have a subset match */
+ if (!*++pat) return NIL; /* % at end, no inferiors permitted */
+ /* scan remainder of string until delimiter */
+ do if (dmatch (s,pat,delim)) return T;
+ while ((*s != delim) && *s++);
+ if (*s && !s[1]) return T; /* ends with delimiter, must be subset */
+ return dmatch (s,pat,delim);/* do new scan */
+ case '*': /* match 0 or more characters */
+ return T; /* unconditional match */
+ case '\0': /* end of pattern */
+ break;
+ default: /* match this character */
+ if (*s) return (*pat == *s) ? dmatch (s+1,pat+1,delim) : NIL;
+ /* end of base, return if at delimiter */
+ else if (*pat == delim) return T;
+ break;
+ }
+ return NIL;
+}
diff --git a/imap/src/osdep/amiga/pseudo.c b/imap/src/osdep/amiga/pseudo.c
new file mode 100644
index 00000000..1aae8a53
--- /dev/null
+++ b/imap/src/osdep/amiga/pseudo.c
@@ -0,0 +1,36 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Pseudo Header Strings
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 September 1996
+ * Last Edited: 30 August 2006
+ */
+
+/* Local sites may wish to alter this text */
+
+char *pseudo_from = "MAILER-DAEMON";
+char *pseudo_name = "Mail System Internal Data";
+char *pseudo_subject = "DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA";
+char *pseudo_msg =
+ "This text is part of the internal format of your mail folder, and is not\na real message. It is created automatically by the mail system software.\nIf deleted, important folder data will be lost, and it will be re-created\nwith the data reset to initial values."
+ ;
diff --git a/imap/src/osdep/amiga/pseudo.h b/imap/src/osdep/amiga/pseudo.h
new file mode 100644
index 00000000..c9c07628
--- /dev/null
+++ b/imap/src/osdep/amiga/pseudo.h
@@ -0,0 +1,30 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Pseudo Header Strings
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 September 1996
+ * Last Edited: 30 August 2006
+ */
+
+
+extern char *pseudo_from,*pseudo_name,*pseudo_subject,*pseudo_msg;
diff --git a/imap/src/osdep/amiga/scandir.c b/imap/src/osdep/amiga/scandir.c
new file mode 100644
index 00000000..878343e8
--- /dev/null
+++ b/imap/src/osdep/amiga/scandir.c
@@ -0,0 +1,81 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Scan directories
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 15 September 2006
+ */
+
+/* Emulator for BSD scandir() call
+ * Accepts: directory name
+ * destination pointer of names array
+ * selection function
+ * comparison function
+ * Returns: number of elements in the array or -1 if error
+ */
+
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar)
+{
+ struct direct *p,*d,**names;
+ int nitems;
+ struct stat stb;
+ long nlmax;
+ DIR *dirp = opendir (dirname);/* open directory and get status poop */
+ if ((!dirp) || (fstat (dirp->dd_fd,&stb) < 0)) return -1;
+ nlmax = stb.st_size / 24; /* guesstimate at number of files */
+ names = (struct direct **) fs_get (nlmax * sizeof (struct direct *));
+ nitems = 0; /* initially none found */
+ while (d = readdir (dirp)) { /* read directory item */
+ /* matches select criterion? */
+ if (select && !(*select) (d)) continue;
+ /* get size of direct record for this file */
+ p = (struct direct *) fs_get (DIR_SIZE (d));
+ p->d_ino = d->d_ino; /* copy the poop */
+ strcpy (p->d_name,d->d_name);
+ if (++nitems >= nlmax) { /* if out of space, try bigger guesstimate */
+ void *s = (void *) names; /* stupid language */
+ nlmax *= 2; /* double it */
+ fs_resize ((void **) &s,nlmax * sizeof (struct direct *));
+ names = (struct direct **) s;
+ }
+ names[nitems - 1] = p; /* store this file there */
+ }
+ closedir (dirp); /* done with directory */
+ /* sort if necessary */
+ if (nitems && compar) qsort (names,nitems,sizeof (struct direct *),compar);
+ *namelist = names; /* return directory */
+ return nitems; /* and size */
+}
+
+/* Alphabetic file name comparision
+ * Accepts: first candidate directory entry
+ * second candidate directory entry
+ * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
+ */
+
+int alphasort (void *d1,void *d2)
+{
+ return strcmp ((*(struct direct **) d1)->d_name,
+ (*(struct direct **) d2)->d_name);
+}
diff --git a/imap/src/osdep/amiga/ssl_none.c b/imap/src/osdep/amiga/ssl_none.c
new file mode 100644
index 00000000..e4dedda7
--- /dev/null
+++ b/imap/src/osdep/amiga/ssl_none.c
@@ -0,0 +1,141 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy (no SSL) authentication/encryption module
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 7 February 2001
+ * Last Edited: 30 August 2006
+ */
+
+/* Init server for SSL
+ * Accepts: server name
+ */
+
+void ssl_server_init (char *server)
+{
+ syslog (LOG_ERR,"This server does not support SSL");
+ exit (1); /* punt this program too */
+}
+
+
+/* Start TLS
+ * Accepts: /etc/services service name
+ * Returns: cpystr'd error string if TLS failed, else NIL for success
+ */
+
+char *ssl_start_tls (char *server)
+{
+ return cpystr ("This server does not support TLS");
+}
+
+/* Get character
+ * Returns: character or EOF
+ */
+
+int PBIN (void)
+{
+ return getchar ();
+}
+
+
+/* Get string
+ * Accepts: destination string pointer
+ * number of bytes available
+ * Returns: destination string pointer or NIL if EOF
+ */
+
+char *PSIN (char *s,int n)
+{
+ return fgets (s,n,stdin);
+}
+
+
+/* Get record
+ * Accepts: destination string pointer
+ * number of bytes to read
+ * Returns: T if success, NIL otherwise
+ */
+
+long PSINR (char *s,unsigned long n)
+{
+ unsigned long i;
+ while (n && ((i = fread (s,1,n,stdin)) || (errno == EINTR))) s += i,n -= i;
+ return n ? NIL : LONGT;
+}
+
+
+/* Wait for input
+ * Accepts: timeout in seconds
+ * Returns: T if have input on stdin, else NIL
+ */
+
+long INWAIT (long seconds)
+{
+ return server_input_wait (seconds);
+}
+
+/* Put character
+ * Accepts: character
+ * Returns: character written or EOF
+ */
+
+int PBOUT (int c)
+{
+ return putchar (c);
+}
+
+
+/* Put string
+ * Accepts: source string pointer
+ * Returns: 0 or EOF if error
+ */
+
+int PSOUT (char *s)
+{
+ return fputs (s,stdout);
+}
+
+
+/* Put record
+ * Accepts: source sized text
+ * Returns: 0 or EOF if error
+ */
+
+int PSOUTR (SIZEDTEXT *s)
+{
+ unsigned char *t;
+ unsigned long i,j;
+ for (t = s->data,i = s->size;
+ (i && ((j = fwrite (t,1,i,stdout)) || (errno == EINTR)));
+ t += j,i -= j);
+ return i ? EOF : NIL;
+}
+
+
+/* Flush output
+ * Returns: 0 or EOF if error
+ */
+
+int PFLUSH (void)
+{
+ return fflush (stdout);
+}
diff --git a/imap/src/osdep/amiga/tcp_ami.c b/imap/src/osdep/amiga/tcp_ami.c
new file mode 100644
index 00000000..974717c0
--- /dev/null
+++ b/imap/src/osdep/amiga/tcp_ami.c
@@ -0,0 +1,797 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Amiga TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 13 January 2008
+ */
+
+#undef write /* don't use redefined write() */
+
+static tcptimeout_t tmoh = NIL; /* TCP timeout handler routine */
+static long ttmo_open = 0; /* TCP timeouts, in seconds */
+static long ttmo_read = 0;
+static long ttmo_write = 0;
+static long allowreversedns = T;/* allow reverse DNS lookup */
+static long tcpdebug = NIL; /* extra TCP debugging telemetry */
+
+extern long maxposint; /* get this from write.c */
+
+/* Local function prototypes */
+
+int tcp_socket_open (struct sockaddr_in *sin,char *tmp,int *ctr,char *hst,
+ unsigned long port);
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd);
+long tcp_abort (TCPSTREAM *stream);
+char *tcp_name (struct sockaddr_in *sin,long flag);
+char *tcp_name_valid (char *s);
+
+/* TCP/IP manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tcp_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_TIMEOUT:
+ tmoh = (tcptimeout_t) value;
+ case GET_TIMEOUT:
+ ret = (void *) tmoh;
+ break;
+ case SET_OPENTIMEOUT:
+ ttmo_open = (long) value;
+ case GET_OPENTIMEOUT:
+ ret = (void *) ttmo_open;
+ break;
+ case SET_READTIMEOUT:
+ ttmo_read = (long) value;
+ case GET_READTIMEOUT:
+ ret = (void *) ttmo_read;
+ break;
+ case SET_WRITETIMEOUT:
+ ttmo_write = (long) value;
+ case GET_WRITETIMEOUT:
+ ret = (void *) ttmo_write;
+ break;
+ case SET_ALLOWREVERSEDNS:
+ allowreversedns = (long) value;
+ case GET_ALLOWREVERSEDNS:
+ ret = (void *) allowreversedns;
+ break;
+ case SET_TCPDEBUG:
+ tcpdebug = (long) value;
+ case GET_TCPDEBUG:
+ ret = (void *) tcpdebug;
+ break;
+ }
+ return ret;
+}
+
+/* TCP/IP open
+ * Accepts: host name
+ * contact service name
+ * contact port number and optional silent flag
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = NIL;
+ int i;
+ int sock = -1;
+ int ctr = 0;
+ int silent = (port & NET_SILENT) ? T : NIL;
+ int *ctrp = (port & NET_NOOPENTIMEOUT) ? NIL : &ctr;
+ char *s;
+ struct sockaddr_in sin;
+ struct hostent *he;
+ char hostname[MAILTMPLEN];
+ char tmp[MAILTMPLEN];
+ struct servent *sv = NIL;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ void *data;
+ port &= 0xffff; /* erase flags */
+ /* lookup service */
+ if (service && (sv = getservbyname (service,"tcp")))
+ port = ntohs (sin.sin_port = sv->s_port);
+ /* copy port number in network format */
+ else sin.sin_port = htons (port);
+ /* The domain literal form is used (rather than simply the dotted decimal
+ as with other Amiga programs) because it has to be a valid "host name"
+ in mailsystem terminology. */
+ /* look like domain literal? */
+ if (host[0] == '[' && host[(strlen (host))-1] == ']') {
+ strcpy (hostname,host+1); /* yes, copy number part */
+ hostname[(strlen (hostname))-1] = '\0';
+ if ((sin.sin_addr.s_addr = inet_addr (hostname)) == -1)
+ sprintf (tmp,"Bad format domain-literal: %.80s",host);
+ else {
+ sin.sin_family = AF_INET; /* family is always Internet */
+ strcpy (hostname,host); /* hostname is user's argument */
+ (*bn) (BLOCK_TCPOPEN,NIL);
+ /* get an open socket for this system */
+ sock = tcp_socket_open (&sin,tmp,ctrp,hostname,port);
+ (*bn) (BLOCK_NONE,NIL);
+ }
+ }
+
+ else { /* lookup host name */
+ if (tcpdebug) {
+ sprintf (tmp,"DNS resolution %.80s",host);
+ mm_log (tmp,TCPDEBUG);
+ }
+ (*bn) (BLOCK_DNSLOOKUP,NIL);/* quell alarms */
+ data = (*bn) (BLOCK_SENSITIVE,NIL);
+ if (!(he = gethostbyname (lcase (strcpy (hostname,host)))))
+ sprintf (tmp,"No such host as %.80s",host);
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ (*bn) (BLOCK_NONE,NIL);
+ if (he) { /* DNS resolution won? */
+ if (tcpdebug) mm_log ("DNS resolution done",TCPDEBUG);
+ /* copy address type */
+ sin.sin_family = he->h_addrtype;
+ /* copy host name */
+ strcpy (hostname,he->h_name);
+#ifdef HOST_NOT_FOUND /* muliple addresses only on DNS systems */
+ for (sock = -1,i = 0; (sock < 0) && (s = he->h_addr_list[i]); i++) {
+ if (i && !silent) mm_log (tmp,WARN);
+ memcpy (&sin.sin_addr,s,he->h_length);
+ (*bn) (BLOCK_TCPOPEN,NIL);
+ sock = tcp_socket_open (&sin,tmp,ctrp,hostname,port);
+ (*bn) (BLOCK_NONE,NIL);
+ }
+#else /* the one true address then */
+ memcpy (&sin.sin_addr,he->h_addr,he->h_length);
+ (*bn) (BLOCK_TCPOPEN,NIL);
+ sock = tcp_socket_open (&sin,tmp,ctrp,hostname,port);
+ (*bn) (BLOCK_NONE,NIL);
+#endif
+ }
+ }
+ if (sock >= 0) { /* won */
+ stream = (TCPSTREAM *) memset (fs_get (sizeof (TCPSTREAM)),0,
+ sizeof (TCPSTREAM));
+ stream->port = port; /* port number */
+ /* init sockets */
+ stream->tcpsi = stream->tcpso = sock;
+ /* stash in the snuck-in byte */
+ if (stream->ictr = ctr) *(stream->iptr = stream->ibuf) = tmp[0];
+ /* copy official host name */
+ stream->host = cpystr (hostname);
+ if (tcpdebug) mm_log ("Stream open and ready for read",TCPDEBUG);
+ }
+ else if (!silent) mm_log (tmp,ERROR);
+ return stream; /* return success */
+}
+
+/* Open a TCP socket
+ * Accepts: Internet socket address block
+ * scratch buffer
+ * pointer to "first byte read in" storage or NIL
+ * host name for error message
+ * port number for error message
+ * Returns: socket if success, else -1 with error string in scratch buffer
+ */
+
+int tcp_socket_open (struct sockaddr_in *sin,char *tmp,int *ctr,char *hst,
+ unsigned long port)
+{
+ int i,ti,sock,flgs;
+ time_t now;
+ struct protoent *pt = getprotobyname ("tcp");
+ fd_set fds,efds;
+ struct timeval tmo;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ void *data = (*bn) (BLOCK_SENSITIVE,NIL);
+ sprintf (tmp,"Trying IP address [%s]",inet_ntoa (sin->sin_addr));
+ mm_log (tmp,NIL);
+ /* make a socket */
+ if ((sock = socket (sin->sin_family,SOCK_STREAM,pt ? pt->p_proto : 0)) < 0) {
+ sprintf (tmp,"Unable to create TCP socket: %s",strerror (errno));
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ return -1;
+ }
+ else if (sock >= FD_SETSIZE) {/* unselectable sockets are useless */
+ sprintf (tmp,"Unable to create selectable TCP socket (%d >= %d)",
+ sock,FD_SETSIZE);
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ close (sock);
+ errno = EMFILE;
+ return -1;
+ }
+ flgs = fcntl (sock,F_GETFL,0);/* get current socket flags */
+ /* set non-blocking if want open timeout */
+ if (ctr) fcntl (sock,F_SETFL,flgs | FNDELAY);
+ /* open connection */
+ while ((i = connect (sock,(struct sockaddr *) sin,
+ sizeof (struct sockaddr_in))) < 0 && (errno == EINTR));
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ if (i < 0) switch (errno) { /* failed? */
+ case EAGAIN: /* DG brain damage */
+ case EINPROGRESS: /* what we expect to happen */
+ case EALREADY: /* or another form of it */
+ case EISCONN: /* restart after interrupt? */
+ case EADDRINUSE: /* restart after interrupt? */
+ break; /* well, not really, it was interrupted */
+ default:
+ sprintf (tmp,"Can't connect to %.80s,%lu: %s",hst,port,strerror (errno));
+ close (sock); /* flush socket */
+ return -1;
+ }
+
+ if (ctr) { /* want open timeout */
+ now = time (0); /* open timeout */
+ ti = ttmo_open ? now + ttmo_open : 0;
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ FD_SET (sock,&fds); /* block for error or readable */
+ FD_SET (sock,&efds);
+ do { /* block under timeout */
+ tmo.tv_sec = ti ? ti - now : 0;
+ i = select (sock+1,&fds,0,&efds,ti ? &tmo : 0);
+ now = time (0); /* fake timeout if interrupt & time expired */
+ if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0;
+ } while ((i < 0) && (errno == EINTR));
+ if (i > 0) { /* success, make sure really connected */
+ fcntl (sock,F_SETFL,flgs);/* restore blocking status */
+ /* This used to be a zero-byte read(), but that crashes Solaris */
+ /* get socket status */
+ while (((i = *ctr = read (sock,tmp,1)) < 0) && (errno == EINTR));
+ }
+ if (i <= 0) { /* timeout or error? */
+ i = i ? errno : ETIMEDOUT;/* determine error code */
+ close (sock); /* flush socket */
+ errno = i; /* return error code */
+ sprintf (tmp,"Connection failed to %.80s,%lu: %s",hst,port,
+ strerror (errno));
+ return -1;
+ }
+ }
+ return sock; /* return the socket */
+}
+
+/* TCP/IP authenticated open
+ * Accepts: host name
+ * service name
+ * returned user name buffer
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL; /* disabled */
+}
+
+/* TCP receive line
+ * Accepts: TCP stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *tcp_getline (TCPSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = tcp_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = tcp_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* TCP receive line or partial line
+ * Accepts: TCP stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!tcp_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!tcp_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* TCP/IP receive buffer
+ * Accepts: TCP/IP stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *s)
+{
+ unsigned long n;
+ /* make sure socket still alive */
+ if (stream->tcpsi < 0) return NIL;
+ /* can transfer bytes from buffer? */
+ if (n = min (size,stream->ictr)) {
+ memcpy (s,stream->iptr,n); /* yes, slurp as much as we can from it */
+ s += n; /* update pointer */
+ stream->iptr +=n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -=n;
+ }
+ if (size) {
+ int i;
+ fd_set fds,efds;
+ struct timeval tmo;
+ time_t t = time (0);
+ blocknotify_t bn=(blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ (*bn) (BLOCK_TCPREAD,NIL);
+ while (size > 0) { /* until request satisfied */
+ time_t tl = time (0);
+ time_t now = tl;
+ time_t ti = ttmo_read ? now + ttmo_read : 0;
+ if (tcpdebug) mm_log ("Reading TCP buffer",TCPDEBUG);
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ FD_SET (stream->tcpsi,&fds);
+ FD_SET (stream->tcpsi,&efds);
+ errno = NIL; /* block and read */
+ do { /* block under timeout */
+ tmo.tv_sec = ti ? ti - now : 0;
+ i = select (stream->tcpsi+1,&fds,0,&efds,ti ? &tmo : 0);
+ now = time (0); /* fake timeout if interrupt & time expired */
+ if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0;
+ } while ((i < 0) && (errno == EINTR));
+ if (i > 0) { /* select says there's data to read? */
+ while (((i = read (stream->tcpsi,s,(int) min (maxposint,size))) < 0) &&
+ (errno == EINTR));
+ if (i < 1) return tcp_abort (stream);
+ s += i; /* point at new place to write */
+ size -= i; /* reduce byte count */
+ if (tcpdebug) mm_log ("Successfully read TCP buffer",TCPDEBUG);
+ }
+ else if (i || !tmoh || !(*tmoh) (now - t,now - tl))
+ return tcp_abort (stream);
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ }
+ *s = '\0'; /* tie off string */
+ return T;
+}
+
+/* TCP/IP receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getdata (TCPSTREAM *stream)
+{
+ int i;
+ fd_set fds,efds;
+ struct timeval tmo;
+ time_t t = time (0);
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (stream->tcpsi < 0) return NIL;
+ (*bn) (BLOCK_TCPREAD,NIL);
+ while (stream->ictr < 1) { /* if nothing in the buffer */
+ time_t tl = time (0); /* start of request */
+ time_t now = tl;
+ time_t ti = ttmo_read ? now + ttmo_read : 0;
+ if (tcpdebug) mm_log ("Reading TCP data",TCPDEBUG);
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ FD_SET (stream->tcpsi,&fds);/* set bit in selection vector */
+ FD_SET(stream->tcpsi,&efds);/* set bit in error selection vector */
+ errno = NIL; /* block and read */
+ do { /* block under timeout */
+ tmo.tv_sec = ti ? ti - now : 0;
+ i = select (stream->tcpsi+1,&fds,0,&efds,ti ? &tmo : 0);
+ now = time (0); /* fake timeout if interrupt & time expired */
+ if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0;
+ } while ((i < 0) && (errno == EINTR));
+ if (i > 0) { /* got data? */
+ while (((i = read (stream->tcpsi,stream->ibuf,BUFLEN)) < 0) &&
+ (errno == EINTR));
+ if (i < 1) return tcp_abort (stream);
+ stream->iptr = stream->ibuf;/* point at TCP buffer */
+ stream->ictr = i; /* set new byte count */
+ if (tcpdebug) mm_log ("Successfully read TCP data",TCPDEBUG);
+ }
+ else if (i || !tmoh || !(*tmoh) (now - t,now - tl))
+ return tcp_abort (stream);/* error or timeout no-continue */
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return T;
+}
+
+/* TCP/IP send string as record
+ * Accepts: TCP/IP stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long tcp_soutr (TCPSTREAM *stream,char *string)
+{
+ return tcp_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* TCP/IP send string
+ * Accepts: TCP/IP stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
+{
+ int i;
+ fd_set fds,efds;
+ struct timeval tmo;
+ time_t t = time (0);
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (stream->tcpso < 0) return NIL;
+ (*bn) (BLOCK_TCPWRITE,NIL);
+ while (size > 0) { /* until request satisfied */
+ time_t tl = time (0); /* start of request */
+ time_t now = tl;
+ time_t ti = ttmo_write ? now + ttmo_write : 0;
+ if (tcpdebug) mm_log ("Writing to TCP",TCPDEBUG);
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ FD_SET (stream->tcpso,&fds);/* set bit in selection vector */
+ FD_SET(stream->tcpso,&efds);/* set bit in error selection vector */
+ errno = NIL; /* block and write */
+ do { /* block under timeout */
+ tmo.tv_sec = ti ? ti - now : 0;
+ i = select (stream->tcpso+1,0,&fds,&efds,ti ? &tmo : 0);
+ now = time (0); /* fake timeout if interrupt & time expired */
+ if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0;
+ } while ((i < 0) && (errno == EINTR));
+ if (i > 0) { /* OK to send data? */
+ while (((i = write (stream->tcpso,string,size)) < 0) &&(errno == EINTR));
+ if (i < 0) return tcp_abort (stream);
+ size -= i; /* how much we sent */
+ string += i;
+ if (tcpdebug) mm_log ("successfully wrote to TCP",TCPDEBUG);
+ }
+ else if (i || !tmoh || !(*tmoh) (now - t,now - tl))
+ return tcp_abort (stream);/* error or timeout no-continue */
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return T; /* all done */
+}
+
+/* TCP/IP close
+ * Accepts: TCP/IP stream
+ */
+
+void tcp_close (TCPSTREAM *stream)
+{
+ tcp_abort (stream); /* nuke the stream */
+ /* flush host names */
+ if (stream->host) fs_give ((void **) &stream->host);
+ if (stream->remotehost) fs_give ((void **) &stream->remotehost);
+ if (stream->localhost) fs_give ((void **) &stream->localhost);
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* TCP/IP abort stream
+ * Accepts: TCP/IP stream
+ * Returns: NIL always
+ */
+
+long tcp_abort (TCPSTREAM *stream)
+{
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (stream->tcpsi >= 0) { /* no-op if no socket */
+ (*bn) (BLOCK_TCPCLOSE,NIL);
+ close (stream->tcpsi); /* nuke the socket */
+ if (stream->tcpsi != stream->tcpso) close (stream->tcpso);
+ stream->tcpsi = stream->tcpso = -1;
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return NIL;
+}
+
+/* TCP/IP get host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_host (TCPSTREAM *stream)
+{
+ return stream->host; /* use tcp_remotehost() if want guarantees */
+}
+
+
+/* TCP/IP get remote host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_remotehost (TCPSTREAM *stream)
+{
+ if (!stream->remotehost) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ stream->remotehost = /* get socket's peer name */
+ (getpeername (stream->tcpsi,(struct sockaddr *) &sin,(void *) &sinlen) ||
+ (sin.sin_family != AF_INET)) ?
+ cpystr (stream->host) : tcp_name (&sin,NIL);
+ }
+ return stream->remotehost;
+}
+
+
+/* TCP/IP return port for this stream
+ * Accepts: TCP/IP stream
+ * Returns: port number for this stream
+ */
+
+unsigned long tcp_port (TCPSTREAM *stream)
+{
+ return stream->port; /* return port number */
+}
+
+
+/* TCP/IP get local host name
+ * Accepts: TCP/IP stream
+ * Returns: local host name
+ */
+
+char *tcp_localhost (TCPSTREAM *stream)
+{
+ if (!stream->localhost) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ stream->localhost = /* get socket's name */
+ ((stream->port & 0xffff000) ||
+ getsockname (stream->tcpsi,(struct sockaddr *) &sin,(void *) &sinlen) ||
+ (sin.sin_family != AF_INET)) ?
+ cpystr (mylocalhost ()) : tcp_name (&sin,NIL);
+ }
+ return stream->localhost; /* return local host name */
+}
+
+/* TCP/IP get client host address (server calls only)
+ * Returns: client host address
+ */
+
+static char *myClientAddr = NIL;
+
+char *tcp_clientaddr ()
+{
+ if (!myClientAddr) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ myClientAddr = /* get stdin's peer name */
+ cpystr (getpeername (0,(struct sockaddr *) &sin,(void *) &sinlen) ?
+ "UNKNOWN" : ((sin.sin_family == AF_INET) ?
+ inet_ntoa (sin.sin_addr) : "NON-IPv4"));
+ }
+ return myClientAddr;
+}
+
+
+/* TCP/IP get client host name (server calls only)
+ * Returns: client host name
+ */
+
+static char *myClientHost = NIL;
+
+char *tcp_clienthost ()
+{
+ if (!myClientHost) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ myClientHost = /* get stdin's peer name */
+ getpeername (0,(struct sockaddr *) &sin,(void *) &sinlen) ?
+ cpystr ("UNKNOWN") : ((sin.sin_family == AF_INET) ?
+ tcp_name (&sin,T) : cpystr ("NON-IPv4"));
+ }
+ return myClientHost;
+}
+
+/* TCP/IP get server host address (server calls only)
+ * Returns: server host address
+ */
+
+static char *myServerAddr = NIL;
+
+char *tcp_serveraddr ()
+{
+ if (!myServerAddr) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ myServerAddr = /* get stdin's peer name */
+ cpystr (getsockname (0,(struct sockaddr *) &sin,(void *) &sinlen) ?
+ "UNKNOWN" : ((sin.sin_family == AF_INET) ?
+ inet_ntoa (sin.sin_addr) : "NON-IPv4"));
+ }
+ return myServerAddr;
+}
+
+
+/* TCP/IP get server host name (server calls only)
+ * Returns: server host name
+ */
+
+static char *myServerHost = NIL;
+static long myServerPort = -1;
+
+char *tcp_serverhost ()
+{
+ if (!myServerHost) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ /* get stdin's name */
+ if (getsockname (0,(struct sockaddr *) &sin,(void *) &sinlen) ||
+ (sin.sin_family != AF_INET)) myServerHost = cpystr (mylocalhost ());
+ else {
+ myServerHost = tcp_name (&sin,NIL);
+ myServerPort = ntohs (sin.sin_port);
+ }
+ }
+ return myServerHost;
+}
+
+
+/* TCP/IP get server port number (server calls only)
+ * Returns: server port number
+ */
+
+long tcp_serverport ()
+{
+ if (!myServerHost) tcp_serverhost ();
+ return myServerPort;
+}
+
+/* TCP/IP return canonical form of host name
+ * Accepts: host name
+ * Returns: canonical form of host name
+ */
+
+char *tcp_canonical (char *name)
+{
+ char *ret,host[MAILTMPLEN];
+ struct hostent *he;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ void *data;
+ /* look like domain literal? */
+ if (name[0] == '[' && name[strlen (name) - 1] == ']') return name;
+ (*bn) (BLOCK_DNSLOOKUP,NIL); /* quell alarms */
+ data = (*bn) (BLOCK_SENSITIVE,NIL);
+ if (tcpdebug) {
+ sprintf (host,"DNS canonicalization %.80s",name);
+ mm_log (host,TCPDEBUG);
+ }
+ /* note that Amiga requires lowercase! */
+ ret = (he = gethostbyname (lcase (strcpy (host,name)))) ?
+ (char *) he->h_name : name;
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ (*bn) (BLOCK_NONE,NIL); /* alarms OK now */
+ if (tcpdebug) mm_log ("DNS canonicalization done",TCPDEBUG);
+ return ret;
+}
+
+
+/* TCP/IP return name from socket
+ * Accepts: socket
+ * verbose flag
+ * Returns: cpystr name
+ */
+
+char *tcp_name (struct sockaddr_in *sin,long flag)
+{
+ char *ret,*t,adr[MAILTMPLEN],tmp[MAILTMPLEN];
+ sprintf (ret = adr,"[%.80s]",inet_ntoa (sin->sin_addr));
+ if (allowreversedns) {
+ struct hostent *he;
+ blocknotify_t bn = (blocknotify_t)mail_parameters(NIL,GET_BLOCKNOTIFY,NIL);
+ void *data;
+ if (tcpdebug) {
+ sprintf (tmp,"Reverse DNS resolution %s",adr);
+ mm_log (tmp,TCPDEBUG);
+ }
+ (*bn) (BLOCK_DNSLOOKUP,NIL);/* quell alarms */
+ data = (*bn) (BLOCK_SENSITIVE,NIL);
+ /* translate address to name */
+ if (t = tcp_name_valid ((he = gethostbyaddr ((char *) &sin->sin_addr,
+ sizeof (struct in_addr),
+ sin->sin_family)) ?
+ (char *) he->h_name : NIL)) {
+ /* produce verbose form if needed */
+ if (flag) sprintf (ret = tmp,"%s %s",t,adr);
+ else ret = t;
+ }
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ (*bn) (BLOCK_NONE,NIL); /* alarms OK now */
+ if (tcpdebug) mm_log ("Reverse DNS resolution done",TCPDEBUG);
+ }
+ return cpystr (ret);
+}
+
+
+/* Validate name
+ * Accepts: domain name
+ * Returns: T if valid, NIL otherwise
+ */
+
+char *tcp_name_valid (char *s)
+{
+ int c;
+ char *ret,*tail;
+ /* must be non-empty and not too long */
+ if ((ret = (s && *s) ? s : NIL) && (tail = ret + NETMAXHOST)) {
+ /* must be alnum, dot, or hyphen */
+ while ((c = *s++) && (s <= tail) &&
+ (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) ||
+ ((c >= '0') && (c <= '9')) || (c == '-') || (c == '.')));
+ if (c) ret = NIL;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/amiga/tcp_ami.h b/imap/src/osdep/amiga/tcp_ami.h
new file mode 100644
index 00000000..d4b29797
--- /dev/null
+++ b/imap/src/osdep/amiga/tcp_ami.h
@@ -0,0 +1,48 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* TCP input buffer */
+
+#define BUFLEN 8192
+
+
+/* TCP I/O stream */
+
+#define TCPSTREAM struct tcp_stream
+TCPSTREAM {
+ char *host; /* host name */
+ unsigned long port; /* port number */
+ char *localhost; /* local host name */
+ char *remotehost; /* remote host name */
+ int tcpsi; /* input socket */
+ int tcpso; /* output socket */
+ int ictr; /* input counter */
+ char *iptr; /* input pointer */
+ char ibuf[BUFLEN]; /* input buffer */
+};
diff --git a/imap/src/osdep/amiga/tenex.c b/imap/src/osdep/amiga/tenex.c
new file mode 100644
index 00000000..eee61fba
--- /dev/null
+++ b/imap/src/osdep/amiga/tenex.c
@@ -0,0 +1,1470 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Tenex mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 22 May 1990
+ * Last Edited: 11 October 2007
+ */
+
+
+/* FILE TIME SEMANTICS
+ *
+ * The atime is the last read time of the file.
+ * The mtime is the last flags update time of the file.
+ * The ctime is the last write time of the file.
+ *
+ * TEXT SIZE SEMANTICS
+ *
+ * Most of the text sizes are in internal (LF-only) form, except for the
+ * msg.text size. Beware.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <sys/stat.h>
+#include "misc.h"
+#include "dummy.h"
+
+/* TENEX I/O stream local data */
+
+typedef struct tenex_local {
+ unsigned int shouldcheck: 1; /* if ping should do a check instead */
+ unsigned int mustcheck: 1; /* if ping must do a check instead */
+ int fd; /* file descriptor for I/O */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* local snarf time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned long uid; /* current text uid */
+ SIZEDTEXT text; /* current text */
+} TENEXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((TENEXLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *tenex_valid (char *name);
+int tenex_isvalid (char *name,char *tmp);
+void *tenex_parameters (long function,void *value);
+void tenex_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void tenex_list (MAILSTREAM *stream,char *ref,char *pat);
+void tenex_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long tenex_create (MAILSTREAM *stream,char *mailbox);
+long tenex_delete (MAILSTREAM *stream,char *mailbox);
+long tenex_rename (MAILSTREAM *stream,char *old,char *newname);
+long tenex_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *tenex_open (MAILSTREAM *stream);
+void tenex_close (MAILSTREAM *stream,long options);
+void tenex_fast (MAILSTREAM *stream,char *sequence,long flags);
+void tenex_flags (MAILSTREAM *stream,char *sequence,long flags);
+char *tenex_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long tenex_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void tenex_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void tenex_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long tenex_ping (MAILSTREAM *stream);
+void tenex_check (MAILSTREAM *stream);
+void tenex_snarf (MAILSTREAM *stream);
+long tenex_expunge (MAILSTREAM *stream,char *sequence,long options);
+long tenex_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long tenex_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+unsigned long tenex_size (MAILSTREAM *stream,unsigned long m);
+char *tenex_file (char *dst,char *name);
+long tenex_parse (MAILSTREAM *stream);
+MESSAGECACHE *tenex_elt (MAILSTREAM *stream,unsigned long msgno);
+void tenex_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
+void tenex_update_status (MAILSTREAM *stream,unsigned long msgno,
+ long syncflag);
+unsigned long tenex_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size);
+
+/* Tenex mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER tenexdriver = {
+ "tenex", /* driver name */
+ DR_LOCAL|DR_MAIL|DR_NOSTICKY|DR_LOCKING,
+ /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ tenex_valid, /* mailbox is valid for us */
+ tenex_parameters, /* manipulate parameters */
+ tenex_scan, /* scan mailboxes */
+ tenex_list, /* list mailboxes */
+ tenex_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ tenex_delete, /* delete mailbox */
+ tenex_rename, /* rename mailbox */
+ tenex_status, /* status of mailbox */
+ tenex_open, /* open mailbox */
+ tenex_close, /* close mailbox */
+ tenex_fast, /* fetch message "fast" attributes */
+ tenex_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ tenex_header, /* fetch message header */
+ tenex_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ tenex_flag, /* modify flags */
+ tenex_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ tenex_ping, /* ping mailbox to see if still alive */
+ tenex_check, /* check for new messages */
+ tenex_expunge, /* expunge deleted messages */
+ tenex_copy, /* copy messages to another mailbox */
+ tenex_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM tenexproto = {&tenexdriver};
+
+/* Tenex mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *tenex_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return tenex_isvalid (name,tmp) ? &tenexdriver : NIL;
+}
+
+
+/* Tenex mail test for valid mailbox
+ * Accepts: mailbox name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int tenex_isvalid (char *name,char *tmp)
+{
+ int fd;
+ int ret = NIL;
+ char *s,file[MAILTMPLEN];
+ struct stat sbuf;
+ time_t tp[2];
+ errno = EINVAL; /* assume invalid argument */
+ /* if file, get its status */
+ if ((s = tenex_file (file,name)) && !stat (s,&sbuf)) {
+ if (!sbuf.st_size) { /* allow empty file if INBOX */
+ if ((s = mailboxfile (tmp,name)) && !*s) ret = T;
+ else errno = 0; /* empty file */
+ }
+ else if ((fd = open (file,O_RDONLY,NIL)) >= 0) {
+ memset (tmp,'\0',MAILTMPLEN);
+ if ((read (fd,tmp,64) >= 0) && (s = strchr (tmp,'\012')) &&
+ (s[-1] != '\015')) { /* valid format? */
+ *s = '\0'; /* tie off header */
+ /* must begin with dd-mmm-yy" */
+ ret = (((tmp[2] == '-' && tmp[6] == '-') ||
+ (tmp[1] == '-' && tmp[5] == '-')) &&
+ (s = strchr (tmp+18,',')) && strchr (s+2,';')) ? T : NIL;
+ }
+ else errno = -1; /* bogus format */
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if (sbuf.st_ctime > sbuf.st_atime) {
+ tp[0] = sbuf.st_atime; /* preserve atime and mtime */
+ tp[1] = sbuf.st_mtime;
+ utime (file,tp); /* set the times */
+ }
+ }
+ }
+ /* in case INBOX but not tenex format */
+ else if ((errno == ENOENT) && !compare_cstring (name,"INBOX")) errno = -1;
+ return ret; /* return what we should */
+}
+
+/* Tenex manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tenex_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = tenex_file ((char *) value,"INBOX");
+ break;
+ }
+ return ret;
+}
+
+
+/* Tenex mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void tenex_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* Tenex mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void tenex_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* Tenex mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void tenex_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* Tenex mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long tenex_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return tenex_rename (stream,mailbox,NIL);
+}
+
+
+/* Tenex mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long tenex_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = T;
+ char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ if (!tenex_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'/')) && !s[1])))) {
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else if ((fd = open (file,O_RDWR,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get exclusive parse/append permission */
+ if ((ld = lockfd (fd,lock,LOCK_EX)) < 0) {
+ MM_LOG ("Unable to lock rename mailbox",ERROR);
+ return NIL;
+ }
+ /* lock out other users */
+ if (flock (fd,LOCK_EX|LOCK_NB)) {
+ close (fd); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ MM_LOG (tmp,ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return NIL;
+ }
+
+ if (newname) { /* want rename? */
+ if (s = strrchr (tmp,'/')) {/* found superior to destination name? */
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp,get_dir_protection (newname)))
+ ret = NIL;
+ else *s = c; /* restore full name */
+ }
+ /* rename the file */
+ if (ret && rename (file,tmp)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ else if (unlink (file)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* recreate file if renamed INBOX */
+ if (ret && !compare_cstring (old,"INBOX")) dummy_create (NIL,"mail.txt");
+ return ret; /* return success */
+}
+
+/* Tenex Mail status
+ * Accepts: mail stream
+ * mailbox name
+ * status flags
+ * Returns: T on success, NIL on failure
+ */
+
+long tenex_status (MAILSTREAM *stream,char *mbx,long flags)
+{
+ MAILSTATUS status;
+ unsigned long i;
+ MAILSTREAM *tstream = NIL;
+ MAILSTREAM *systream = NIL;
+ /* make temporary stream (unless this mbx) */
+ if (!stream && !(stream = tstream =
+ mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
+ status.flags = flags; /* return status values */
+ status.messages = stream->nmsgs;
+ status.recent = stream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
+ if (!mail_elt (stream,i)->seen) status.unseen++;
+ status.uidnext = stream->uid_last + 1;
+ status.uidvalidity = stream->uid_validity;
+ /* calculate post-snarf results */
+ if (!status.recent && stream->inbox &&
+ (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) {
+ status.messages += systream->nmsgs;
+ status.recent += systream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1; i <= systream->nmsgs; i++)
+ if (!mail_elt (systream,i)->seen) status.unseen++;
+ /* kludge but probably good enough */
+ status.uidnext += systream->nmsgs;
+ }
+ MM_STATUS(stream,mbx,&status);/* pass status to main program */
+ if (tstream) mail_close (tstream);
+ if (systream) mail_close (systream);
+ return T; /* success */
+}
+
+/* Tenex mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *tenex_open (MAILSTREAM *stream)
+{
+ int fd,ld;
+ char tmp[MAILTMPLEN];
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&tenexproto);
+ if (stream->local) fatal ("tenex recycle stream");
+ user_flags (stream); /* set up user flags */
+ /* canonicalize the mailbox name */
+ if (!tenex_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ MM_LOG (tmp,ERROR);
+ }
+ if (stream->rdonly ||
+ (fd = open (tmp,O_RDWR,NIL)) < 0) {
+ if ((fd = open (tmp,O_RDONLY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else if (!stream->rdonly) { /* got it, but readonly */
+ MM_LOG ("Can't get write access to mailbox, access is readonly",WARN);
+ stream->rdonly = T;
+ }
+ }
+ stream->local = fs_get (sizeof (TENEXLOCAL));
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE);
+ LOCAL->text.size = CHUNKSIZE - 1;
+
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ LOCAL->fd = fd; /* bind the file */
+ /* flush old name */
+ fs_give ((void **) &stream->mailbox);
+ /* save canonical name */
+ stream->mailbox = cpystr (tmp);
+ /* get shared parse permission */
+ if ((ld = lockfd (fd,tmp,LOCK_SH)) < 0) {
+ MM_LOG ("Unable to lock open mailbox",ERROR);
+ return NIL;
+ }
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH); /* lock the file */
+ (*bn) (BLOCK_NONE,NIL);
+ unlockfd (ld,tmp); /* release shared parse permission */
+ LOCAL->filesize = 0; /* initialize parsed file size */
+ /* time not set up yet */
+ LOCAL->lastsnarf = LOCAL->filetime = 0;
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (tenex_ping (stream) && !stream->nmsgs)
+ MM_LOG ("Mailbox is empty",(long) NIL);
+ if (!LOCAL) return NIL; /* failure if stream died */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft =
+ stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ return stream; /* return stream to caller */
+}
+
+/* Tenex mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void tenex_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ if (options & CL_EXPUNGE) tenex_expunge (stream,NIL,NIL);
+ stream->silent = silent; /* restore previous status */
+ flock (LOCAL->fd,LOCK_UN); /* unlock local file */
+ close (LOCAL->fd); /* close the local file */
+ /* free local text buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* Tenex mail fetch fast data
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ */
+
+void tenex_fast (MAILSTREAM *stream,char *sequence,long flags)
+{
+ STRING bs;
+ MESSAGECACHE *elt;
+ unsigned long i;
+ if (stream && LOCAL &&
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ if (!elt->rfc822_size) { /* have header size yet? */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.special.text.size,L_SET);
+ /* resize bigbuf if necessary */
+ if (LOCAL->buflen < elt->private.msg.full.text.size) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buflen = elt->private.msg.full.text.size;
+ LOCAL->buf = (char *) fs_get (LOCAL->buflen + 1);
+ }
+ /* tie off string */
+ LOCAL->buf[elt->private.msg.full.text.size] = '\0';
+ /* read in the message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.full.text.size);
+ INIT (&bs,mail_string,(void *) LOCAL->buf,
+ elt->private.msg.full.text.size);
+ /* calculate its CRLF size */
+ elt->rfc822_size = strcrlflen (&bs);
+ }
+ tenex_elt (stream,i); /* get current flags from file */
+ }
+}
+
+
+/* Tenex mail fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ * Sniffs at file to get flags
+ */
+
+void tenex_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ unsigned long i;
+ if (stream && LOCAL &&
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if (mail_elt (stream,i)->sequence) tenex_elt (stream,i);
+}
+
+/* TENEX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *tenex_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ char *s;
+ unsigned long i;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get to header position */
+ lseek (LOCAL->fd,tenex_hdrpos (stream,msgno,&i),L_SET);
+ if (flags & FT_INTERNAL) {
+ if (i > LOCAL->buflen) { /* resize if not enough space */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get (LOCAL->buflen = i + 1);
+ }
+ /* slurp the data */
+ read (LOCAL->fd,LOCAL->buf,*length = i);
+ }
+ else {
+ s = (char *) fs_get (i + 1);/* get readin buffer */
+ s[i] = '\0'; /* tie off string */
+ read (LOCAL->fd,s,i); /* slurp the data */
+ /* make CRLF copy of string */
+ *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s,i);
+ fs_give ((void **) &s); /* free readin buffer */
+ }
+ return (char *) LOCAL->buf;
+}
+
+/* TENEX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T, always
+ */
+
+long tenex_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ char *s;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ /* get message status */
+ elt = tenex_elt (stream,msgno);
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ elt->seen = T; /* mark message as seen */
+ /* recalculate status */
+ tenex_update_status (stream,msgno,T);
+ MM_FLAGS (stream,msgno);
+ }
+ if (flags & FT_INTERNAL) { /* if internal representation wanted */
+ /* find header position */
+ i = tenex_hdrpos (stream,msgno,&j);
+ if (i > LOCAL->buflen) { /* resize if not enough space */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get (LOCAL->buflen = i + 1);
+ }
+ /* go to text position */
+ lseek (LOCAL->fd,i + j,L_SET);
+ /* slurp the data */
+ read (LOCAL->fd,LOCAL->buf,i);
+ /* set up stringstruct for internal */
+ INIT (bs,mail_string,LOCAL->buf,i);
+ }
+ else { /* normal form, previous text cached? */
+ if (elt->private.uid == LOCAL->uid)
+ i = elt->private.msg.text.text.size;
+ else { /* not cached, cache it now */
+ LOCAL->uid = elt->private.uid;
+ /* find header position */
+ i = tenex_hdrpos (stream,msgno,&j);
+ /* go to text position */
+ lseek (LOCAL->fd,i + j,L_SET);
+ s = (char *) fs_get ((i = tenex_size (stream,msgno) - j) + 1);
+ s[i] = '\0'; /* tie off string */
+ read (LOCAL->fd,s,i); /* slurp the data */
+ /* make CRLF copy of string */
+ i = elt->private.msg.text.text.size =
+ strcrlfcpy (&LOCAL->text.data,&LOCAL->text.size,s,i);
+ fs_give ((void **) &s); /* free readin buffer */
+ }
+ /* set up stringstruct */
+ INIT (bs,mail_string,LOCAL->text.data,i);
+ }
+ return T; /* success */
+}
+
+/* Tenex mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ */
+
+void tenex_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ time_t tp[2];
+ struct stat sbuf;
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* make sure read comes after all that */
+ utime (stream->mailbox,tp);
+ }
+}
+
+
+/* Tenex mail per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void tenex_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ struct stat sbuf;
+ /* maybe need to do a checkpoint? */
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ LOCAL->filetime = 0; /* don't do this test for any other messages */
+ }
+ /* recalculate status */
+ tenex_update_status (stream,elt->msgno,NIL);
+}
+
+/* Tenex mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long tenex_ping (MAILSTREAM *stream)
+{
+ unsigned long i = 1;
+ long r = T;
+ int ld;
+ char lock[MAILTMPLEN];
+ struct stat sbuf;
+ if (stream && LOCAL) { /* only if stream already open */
+ fstat (LOCAL->fd,&sbuf); /* get current file poop */
+ if (LOCAL->filetime && !(LOCAL->mustcheck || LOCAL->shouldcheck) &&
+ (LOCAL->filetime < sbuf.st_mtime)) LOCAL->shouldcheck = T;
+ /* check for changed message status */
+ if (LOCAL->mustcheck || LOCAL->shouldcheck) {
+ LOCAL->filetime = sbuf.st_mtime;
+ if (LOCAL->shouldcheck) /* babble when we do this unilaterally */
+ MM_NOTIFY (stream,"[CHECK] Checking for flag updates",NIL);
+ while (i <= stream->nmsgs) tenex_elt (stream,i++);
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ }
+ /* get shared parse/append permission */
+ if ((sbuf.st_size != LOCAL->filesize) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) {
+ /* parse resulting mailbox */
+ r = (tenex_parse (stream)) ? T : NIL;
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ if (LOCAL) { /* stream must still be alive */
+ /* snarf if this is a read-write inbox */
+ if (stream->inbox && !stream->rdonly) {
+ tenex_snarf (stream);
+ fstat (LOCAL->fd,&sbuf);/* see if file changed now */
+ if ((sbuf.st_size != LOCAL->filesize) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) {
+ /* parse resulting mailbox */
+ r = (tenex_parse (stream)) ? T : NIL;
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ }
+ }
+ }
+ return r; /* return result of the parse */
+}
+
+
+/* Tenex mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void tenex_check (MAILSTREAM *stream)
+{
+ /* mark that a check is desired */
+ if (LOCAL) LOCAL->mustcheck = T;
+ if (tenex_ping (stream)) MM_LOG ("Check completed",(long) NIL);
+}
+
+/* Tenex mail snarf messages from system inbox
+ * Accepts: MAIL stream
+ */
+
+void tenex_snarf (MAILSTREAM *stream)
+{
+ unsigned long i = 0;
+ unsigned long j,r,hdrlen,txtlen;
+ struct stat sbuf;
+ char *hdr,*txt,lock[MAILTMPLEN],tmp[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ MAILSTREAM *sysibx = NIL;
+ int ld;
+ /* give up if can't get exclusive permission */
+ if ((time (0) >= (LOCAL->lastsnarf +
+ (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) &&
+ strcmp (sysinbox (),stream->mailbox) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) >= 0)) {
+ MM_CRITICAL (stream); /* go critical */
+ /* sizes match and anything in sysinbox? */
+ if (!stat (sysinbox (),&sbuf) && sbuf.st_size &&
+ !fstat (LOCAL->fd,&sbuf) && (sbuf.st_size == LOCAL->filesize) &&
+ (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
+ (!sysibx->rdonly) && (r = sysibx->nmsgs)) {
+ /* yes, go to end of file in our mailbox */
+ lseek (LOCAL->fd,sbuf.st_size,L_SET);
+ /* for each message in sysibx mailbox */
+ while (r && (++i <= sysibx->nmsgs)) {
+ /* snarf message from system INBOX */
+ hdr = cpystr (mail_fetchheader_full(sysibx,i,NIL,&hdrlen,FT_INTERNAL));
+ txt = mail_fetchtext_full (sysibx,i,&txtlen,FT_INTERNAL|FT_PEEK);
+ /* if have a message */
+ if (j = hdrlen + txtlen) {
+ /* calculate header line */
+ mail_date (LOCAL->buf,elt = mail_elt (sysibx,i));
+ sprintf (LOCAL->buf + strlen (LOCAL->buf),
+ ",%lu;0000000000%02o\n",j,(unsigned)
+ ((fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ /* copy message */
+ if ((write (LOCAL->fd,LOCAL->buf,strlen (LOCAL->buf)) < 0) ||
+ (write (LOCAL->fd,hdr,hdrlen) < 0) ||
+ (write (LOCAL->fd,txt,txtlen) < 0)) r = 0;
+ }
+ fs_give ((void **) &hdr);
+ }
+
+ /* make sure all the updates take */
+ if (fsync (LOCAL->fd)) r = 0;
+ if (r) { /* delete all the messages we copied */
+ if (r == 1) strcpy (tmp,"1");
+ else sprintf (tmp,"1:%lu",r);
+ mail_flag (sysibx,tmp,"\\Deleted",ST_SET);
+ mail_expunge (sysibx); /* now expunge all those messages */
+ }
+ else {
+ sprintf (LOCAL->buf,"Can't copy new mail: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,WARN);
+ ftruncate (LOCAL->fd,sbuf.st_size);
+ }
+ fstat (LOCAL->fd,&sbuf); /* yes, get current file size */
+ LOCAL->filetime = sbuf.st_mtime;
+ }
+ if (sysibx) mail_close (sysibx);
+ MM_NOCRITICAL (stream); /* release critical */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ LOCAL->lastsnarf = time (0);/* note time of last snarf */
+ }
+}
+
+/* Tenex mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long tenex_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ time_t tp[2];
+ struct stat sbuf;
+ off_t pos = 0;
+ int ld;
+ unsigned long i = 1;
+ unsigned long j,k,m,recent;
+ unsigned long n = 0;
+ unsigned long delta = 0;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (!(ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ tenex_ping (stream))); /* parse sequence if given, ping stream */
+ else if (stream->rdonly) MM_LOG ("Expunge ignored on readonly mailbox",WARN);
+ else {
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ }
+ /* The cretins who designed flock() created a window of vulnerability in
+ * upgrading locks from shared to exclusive or downgrading from exclusive
+ * to shared. Rather than maintain the lock at shared status at a minimum,
+ * flock() actually *releases* the former lock. Obviously they never talked
+ * to any database guys. Fortunately, we have the parse/append permission
+ * lock. If we require this lock before going exclusive on the mailbox,
+ * another process can not sneak in and steal the exclusive mailbox lock on
+ * us, because it will block on trying to get parse/append permission first.
+ */
+ /* get exclusive parse/append permission */
+ if ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) < 0)
+ MM_LOG ("Unable to lock expunge mailbox",ERROR);
+ /* make sure see any newly-arrived messages */
+ else if (!tenex_parse (stream));
+ /* get exclusive access */
+ else if (flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH);/* recover previous lock */
+ (*bn) (BLOCK_NONE,NIL);
+ MM_LOG ("Can't expunge because mailbox is in use by another process",
+ ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+
+ else {
+ MM_CRITICAL (stream); /* go critical */
+ recent = stream->recent; /* get recent now that pinged and locked */
+ /* for each message */
+ while (i <= stream->nmsgs) {
+ /* get cache element */
+ elt = tenex_elt (stream,i);
+ /* number of bytes to smash or preserve */
+ k = elt->private.special.text.size + tenex_size (stream,i);
+ /* if need to expunge this message */
+ if (elt->deleted && (sequence ? elt->sequence : T)) {
+ /* if recent, note one less recent message */
+ if (elt->recent) --recent;
+ delta += k; /* number of bytes to delete */
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else if (i++ && delta) {/* preserved message */
+ /* first byte to preserve */
+ j = elt->private.special.offset;
+ do { /* read from source position */
+ m = min (k,LOCAL->buflen);
+ lseek (LOCAL->fd,j,L_SET);
+ read (LOCAL->fd,LOCAL->buf,m);
+ pos = j - delta; /* write to destination position */
+ lseek (LOCAL->fd,pos,L_SET);
+ while (T) {
+ lseek (LOCAL->fd,pos,L_SET);
+ if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ pos += m; /* new position */
+ j += m; /* next chunk, perhaps */
+ } while (k -= m); /* until done */
+ /* note the new address of this text */
+ elt->private.special.offset -= delta;
+ }
+ /* preserved but no deleted messages */
+ else pos = elt->private.special.offset + k;
+ }
+
+ if (n) { /* truncate file after last message */
+ if (pos != (LOCAL->filesize -= delta)) {
+ sprintf (LOCAL->buf,
+ "Calculated size mismatch %lu != %lu, delta = %lu",
+ (unsigned long) pos,(unsigned long) LOCAL->filesize,delta);
+ MM_LOG (LOCAL->buf,WARN);
+ LOCAL->filesize = pos;/* fix it then */
+ }
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ sprintf (LOCAL->buf,"Expunged %lu messages",n);
+ /* output the news */
+ MM_LOG (LOCAL->buf,(long) NIL);
+ }
+ else MM_LOG ("No messages deleted, so no update needed",(long) NIL);
+ fsync (LOCAL->fd); /* force disk update */
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* reset atime to now */
+ utime (stream->mailbox,tp);
+ MM_NOCRITICAL (stream); /* release critical */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH);/* allow sharers again */
+ (*bn) (BLOCK_NONE,NIL);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+ }
+ return LONGT;
+}
+
+/* Tenex mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long tenex_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ time_t tp[2];
+ MESSAGECACHE *elt;
+ unsigned long i,j,k;
+ long ret = LONGT;
+ int fd,ld;
+ char file[MAILTMPLEN],lock[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ /* make sure valid mailbox */
+ if (!tenex_isvalid (mailbox,LOCAL->buf)) switch (errno) {
+ case ENOENT: /* no such file? */
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid Tenex-format mailbox name: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a Tenex-format mailbox: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* got file? */
+ if ((fd = open (tenex_file(file,mailbox),O_RDWR,NIL)) < 0) {
+ sprintf (LOCAL->buf,"Unable to open copy mailbox: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ MM_CRITICAL (stream); /* go critical */
+ /* get exclusive parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) {
+ MM_LOG ("Unable to lock copy mailbox",ERROR);
+ MM_NOCRITICAL (stream);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
+
+ /* for each requested message */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ /* number of bytes to copy */
+ k = elt->private.special.text.size + tenex_size (stream,i);
+ do { /* read from source position */
+ j = min (k,LOCAL->buflen);
+ read (LOCAL->fd,LOCAL->buf,j);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ } while (ret && (k -= j));/* until done */
+ }
+ /* make sure all the updates take */
+ if (!(ret && (ret = !fsync (fd)))) {
+ sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ }
+ if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */
+ /* else preserve \Marked status */
+ else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
+ tp[1] = sbuf.st_mtime; /* preserve mtime */
+ utime (file,tp); /* set the times */
+ close (fd); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ MM_NOCRITICAL (stream); /* release critical */
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE)) {
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = tenex_elt (stream,i))->sequence) {
+ elt->deleted = T; /* mark message deleted */
+ /* recalculate status */
+ tenex_update_status (stream,i,NIL);
+ }
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* make sure atime remains greater */
+ utime (stream->mailbox,tp);
+ }
+ }
+ if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
+ MM_LOG ("Can not return meaningful COPYUID with this mailbox format",WARN);
+ return ret;
+}
+
+/* Tenex mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long tenex_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd,ld,c;
+ char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ time_t tp[2];
+ FILE *df;
+ MESSAGECACHE elt;
+ long f;
+ unsigned long i,j,uf,size;
+ STRING *message;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = user_flags (&tenexproto);
+ /* make sure valid mailbox */
+ if (!tenex_isvalid (mailbox,tmp)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!compare_cstring (mailbox,"INBOX")) dummy_create (NIL,"mail.txt");
+ else {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* falls through */
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid TENEX-format mailbox name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a TENEX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get first message */
+ if (!MM_APPEND (af) (stream,data,&flags,&date,&message)) return NIL;
+
+ /* open destination mailbox */
+ if (((fd = open (tenex_file (file,mailbox),O_WRONLY|O_APPEND,NIL)) < 0) ||
+ !(df = fdopen (fd,"ab"))) {
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) {
+ MM_LOG ("Unable to lock append mailbox",ERROR);
+ close (fd);
+ return NIL;
+ }
+ MM_CRITICAL (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ errno = 0;
+ do { /* parse flags */
+ if (!SIZE (message)) { /* guard against zero-length */
+ MM_LOG ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ f = mail_parse_flags (stream,flags,&i);
+ /* reverse bits (dontcha wish we had CIRC?) */
+ for (uf = 0; i; uf |= 1 << (29 - find_rightmost_bit (&i)));
+ if (date) { /* parse date if given */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* mark failure */
+ break;
+ }
+ mail_date (tmp,&elt); /* write preseved date */
+ }
+ else internal_date (tmp); /* get current date in IMAP format */
+ i = GETPOS (message); /* remember current position */
+ for (j = SIZE (message), size = 0; j; --j)
+ if (SNX (message) != '\015') ++size;
+ SETPOS (message,i); /* restore position */
+ /* write header */
+ if (fprintf (df,"%s,%lu;%010lo%02lo\n",tmp,size,uf,(unsigned long) f) < 0)
+ ret = NIL;
+ else { /* write message */
+ while (size) if ((c = 0xff & SNX (message)) != '\015') {
+ if (putc (c,df) != EOF) --size;
+ else break;
+ }
+ /* get next message */
+ if (size || !MM_APPEND (af) (stream,data,&flags,&date,&message))
+ ret = NIL;
+ }
+ } while (ret && message);
+ /* if error... */
+ if (!ret || (fflush (df) == EOF)) {
+ ftruncate (fd,sbuf.st_size);/* revert file */
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ if (errno) {
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ ret = NIL;
+ }
+ if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */
+ /* else preserve \Marked status */
+ else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
+ tp[1] = sbuf.st_mtime; /* preserve mtime */
+ utime (file,tp); /* set the times */
+ fclose (df); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ MM_NOCRITICAL (stream); /* release critical */
+ if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
+ MM_LOG ("Can not return meaningful APPENDUID with this mailbox format",
+ WARN);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* Tenex mail return internal message size in bytes
+ * Accepts: MAIL stream
+ * message #
+ * Returns: internal size of message
+ */
+
+unsigned long tenex_size (MAILSTREAM *stream,unsigned long m)
+{
+ MESSAGECACHE *elt = mail_elt (stream,m);
+ return ((m < stream->nmsgs) ? mail_elt (stream,m+1)->private.special.offset :
+ LOCAL->filesize) -
+ (elt->private.special.offset + elt->private.special.text.size);
+}
+
+
+/* Tenex mail generate file string
+ * Accepts: temporary buffer to write into
+ * mailbox name string
+ * Returns: local file string or NIL if failure
+ */
+
+char *tenex_file (char *dst,char *name)
+{
+ char tmp[MAILTMPLEN];
+ char *s = mailboxfile (dst,name);
+ /* return our standard inbox */
+ return (s && !*s) ? mailboxfile (dst,tenex_isvalid ("~/INBOX",tmp) ?
+ "~/INBOX" : "mail.txt") : s;
+}
+
+/* Tenex mail parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long tenex_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = NIL;
+ unsigned char c,*s,*t,*x;
+ char tmp[MAILTMPLEN];
+ unsigned long i,j;
+ long curpos = LOCAL->filesize;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ short added = NIL;
+ short silent = stream->silent;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %lu to %lu!",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ stream->silent = T; /* don't pass up exists events yet */
+ while (sbuf.st_size - curpos){/* while there is stuff to parse */
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,L_SET);
+ if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
+ sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size,
+ i ? strerror (errno) : "no data read");
+ MM_LOG (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
+ if (!(s = strchr (LOCAL->buf,'\012'))) {
+ sprintf (tmp,"Unable to find newline at %lu in %lu bytes, text: %s",
+ (unsigned long) curpos,i,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ *s = '\0'; /* tie off header line */
+ i = (s + 1) - LOCAL->buf; /* note start of text offset */
+ if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
+ sprintf (tmp,"Unable to parse internal header at %lu: %s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ *s++ = '\0'; *t++ = '\0'; /* tie off fields */
+
+ added = T; /* note that a new message was added */
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ /* instantiate an elt for this message */
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ elt->private.uid = ++stream->uid_last;
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* in case error */
+ elt->private.special.text.size = 0;
+ /* header size not known yet */
+ elt->private.msg.header.text.size = 0;
+ x = s; /* parse the header components */
+ if (mail_parse_date (elt,LOCAL->buf) &&
+ (elt->private.msg.full.text.size = strtoul (s,(char **) &s,10)) &&
+ (!(s && *s)) && isdigit (t[0]) && isdigit (t[1]) && isdigit (t[2]) &&
+ isdigit (t[3]) && isdigit (t[4]) && isdigit (t[5]) &&
+ isdigit (t[6]) && isdigit (t[7]) && isdigit (t[8]) &&
+ isdigit (t[9]) && isdigit (t[10]) && isdigit (t[11]) && !t[12])
+ elt->private.special.text.size = i;
+ else { /* oops */
+ sprintf (tmp,"Unable to parse internal header elements at %ld: %s,%s;%s",
+ curpos,(char *) LOCAL->buf,(char *) x,(char *) t);
+ MM_LOG (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ /* make sure didn't run off end of file */
+ if ((curpos += (elt->private.msg.full.text.size + i)) > sbuf.st_size) {
+ sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
+ elt->private.special.offset,(unsigned long) curpos,
+ (unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ c = t[10]; /* remember first system flags byte */
+ t[10] = '\0'; /* tie off flags */
+ j = strtoul (t,NIL,8); /* get user flags value */
+ t[10] = c; /* restore first system flags byte */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ /* calculate system flags */
+ if ((j = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T;
+ if (j & fDELETED) elt->deleted = T;
+ if (j & fFLAGGED) elt->flagged = T;
+ if (j & fANSWERED) elt->answered = T;
+ if (j & fDRAFT) elt->draft = T;
+ if (!(j & fOLD)) { /* newly arrived message? */
+ elt->recent = T;
+ recent++; /* count up a new recent message */
+ /* mark it as old */
+ tenex_update_status (stream,nmsgs,NIL);
+ }
+ }
+ fsync (LOCAL->fd); /* make sure all the fOLD flags take */
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
+ LOCAL->filetime = sbuf.st_mtime;
+ if (added && !stream->rdonly){/* make sure atime updated */
+ time_t tp[2];
+ tp[0] = time (0);
+ tp[1] = LOCAL->filetime;
+ utime (stream->mailbox,tp);
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return LONGT; /* return the winnage */
+}
+
+/* Tenex get cache element with status updating from file
+ * Accepts: MAIL stream
+ * message number
+ * Returns: cache element
+ */
+
+MESSAGECACHE *tenex_elt (MAILSTREAM *stream,unsigned long msgno)
+{
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
+ old.answered = elt->answered; old.draft = elt->draft;
+ old.user_flags = elt->user_flags;
+ tenex_read_flags (stream,elt);
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
+ (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
+ MM_FLAGS (stream,msgno); /* let top level know */
+ return elt;
+}
+
+/* Tenex read flags from file
+ * Accepts: MAIL stream
+ * Returns: cache element
+ */
+
+void tenex_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ unsigned long i,j;
+ /* noop if readonly and have valid flags */
+ if (stream->rdonly && elt->valid) return;
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 13,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,12) < 0) {
+ sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ /* calculate system flags */
+ i = (((LOCAL->buf[10]-'0') * 8) + LOCAL->buf[11]-'0');
+ elt->seen = i & fSEEN ? T : NIL; elt->deleted = i & fDELETED ? T : NIL;
+ elt->flagged = i & fFLAGGED ? T : NIL;
+ elt->answered = i & fANSWERED ? T : NIL; elt->draft = i & fDRAFT ? T : NIL;
+ LOCAL->buf[10] = '\0'; /* tie off flags */
+ j = strtoul(LOCAL->buf,NIL,8);/* get user flags value */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ elt->valid = T; /* have valid flags now */
+}
+
+/* Tenex update status string
+ * Accepts: MAIL stream
+ * message number
+ * flag saying whether or not to sync
+ */
+
+void tenex_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag)
+{
+ time_t tp[2];
+ struct stat sbuf;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ unsigned long j,k = 0;
+ /* readonly */
+ if (stream->rdonly || !elt->valid) tenex_read_flags (stream,elt);
+ else { /* readwrite */
+ j = elt->user_flags; /* get user flags */
+ /* reverse bits (dontcha wish we had CIRC?) */
+ while (j) k |= 1 << (29 - find_rightmost_bit (&j));
+ /* print new flag string */
+ sprintf (LOCAL->buf,"%010lo%02o",k,(unsigned)
+ (fOLD + (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ /* get to that place in the file */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 13,L_SET);
+ /* write new flags */
+ write (LOCAL->fd,LOCAL->buf,12);
+ if (syncflag) { /* sync if requested */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* make sure read is later */
+ utime (stream->mailbox,tp);
+ }
+ }
+}
+
+/* Tenex locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * Returns: position of header in file
+ */
+
+unsigned long tenex_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size)
+{
+ unsigned long siz;
+ long i = 0;
+ char c = '\0';
+ char *s = NIL;
+ MESSAGECACHE *elt = tenex_elt (stream,msgno);
+ unsigned long ret = elt->private.special.offset +
+ elt->private.special.text.size;
+ unsigned long msiz = tenex_size (stream,msgno);
+ /* is header size known? */
+ if (!(*size = elt->private.msg.header.text.size)) {
+ lseek (LOCAL->fd,ret,L_SET);/* get to header position */
+ /* search message for LF LF */
+ for (siz = 0; siz < msiz; siz++) {
+ if (--i <= 0) /* read another buffer as necessary */
+ read (LOCAL->fd,s = LOCAL->buf,i = min (msiz-siz,(long) MAILTMPLEN));
+ /* two newline sequence? */
+ if ((c == '\012') && (*s == '\012')) {
+ /* yes, note for later */
+ elt->private.msg.header.text.size = (*size = siz + 1);
+
+ return ret; /* return to caller */
+ }
+ else c = *s++; /* next character */
+ }
+ /* header consumes entire message */
+ elt->private.msg.header.text.size = *size = msiz;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/amiga/tz_bsd.c b/imap/src/osdep/amiga/tz_bsd.c
new file mode 100644
index 00000000..75ee624c
--- /dev/null
+++ b/imap/src/osdep/amiga/tz_bsd.c
@@ -0,0 +1,38 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: BSD-style Time Zone String
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 30 August 1994
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Append local timezone name
+ * Accepts: destination string
+ */
+
+void rfc822_timezone (char *s,void *t)
+{
+ /* append timezone from tm struct */
+ sprintf (s + strlen (s)," (%.50s)",((struct tm *) t)->tm_zone);
+}
diff --git a/imap/src/osdep/amiga/unix.c b/imap/src/osdep/amiga/unix.c
new file mode 100644
index 00000000..be3c437b
--- /dev/null
+++ b/imap/src/osdep/amiga/unix.c
@@ -0,0 +1,2708 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX mail routines
+ *
+ * Author: Mark Crispin
+ * UW Technology
+ * University of Washington
+ * Seattle, WA 98195
+ * Internet: MRC@Washington.EDU
+ *
+ * Date: 20 December 1989
+ * Last Edited: 27 March 2008
+ */
+
+
+/* DEDICATION
+ *
+ * This file is dedicated to my dog, Unix, also known as Yun-chan and
+ * Unix J. Terwilliker Jehosophat Aloysius Monstrosity Animal Beast. Unix
+ * passed away at the age of 11 1/2 on September 14, 1996, 12:18 PM PDT, after
+ * a two-month bout with cirrhosis of the liver.
+ *
+ * He was a dear friend, and I miss him terribly.
+ *
+ * Lift a leg, Yunie. Luv ya forever!!!!
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <signal.h>
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <sys/stat.h>
+#include "unix.h"
+#include "pseudo.h"
+#include "fdstring.h"
+#include "misc.h"
+#include "dummy.h"
+
+/* UNIX I/O stream local data */
+
+typedef struct unix_local {
+ unsigned int dirty : 1; /* disk copy needs updating */
+ unsigned int ddirty : 1; /* double-dirty, ping becomes checkpoint */
+ unsigned int pseudo : 1; /* uses a pseudo message */
+ unsigned int appending : 1; /* don't mark new messages as old */
+ int fd; /* mailbox file descriptor */
+ int ld; /* lock file descriptor */
+ char *lname; /* lock file name */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* last snarf time (for mbox driver) */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned long uid; /* current text uid */
+ SIZEDTEXT text; /* current text */
+ unsigned long textlen; /* current text length */
+ char *line; /* returned line */
+ char *linebuf; /* line readin buffer */
+ unsigned long linebuflen; /* current line readin buffer length */
+} UNIXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((UNIXLOCAL *) stream->local)
+
+
+/* UNIX protected file structure */
+
+typedef struct unix_file {
+ MAILSTREAM *stream; /* current stream */
+ off_t curpos; /* current file position */
+ off_t protect; /* protected position */
+ off_t filepos; /* current last written file position */
+ char *buf; /* overflow buffer */
+ size_t buflen; /* current overflow buffer length */
+ char *bufpos; /* current buffer position */
+} UNIXFILE;
+
+/* Function prototypes */
+
+DRIVER *unix_valid (char *name);
+long unix_isvalid_fd (int fd);
+void *unix_parameters (long function,void *value);
+void unix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void unix_list (MAILSTREAM *stream,char *ref,char *pat);
+void unix_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long unix_create (MAILSTREAM *stream,char *mailbox);
+long unix_delete (MAILSTREAM *stream,char *mailbox);
+long unix_rename (MAILSTREAM *stream,char *old,char *newname);
+MAILSTREAM *unix_open (MAILSTREAM *stream);
+void unix_close (MAILSTREAM *stream,long options);
+char *unix_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long unix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+char *unix_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
+ unsigned long *length,long flags);
+void unix_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long unix_ping (MAILSTREAM *stream);
+void unix_check (MAILSTREAM *stream);
+long unix_expunge (MAILSTREAM *stream,char *sequence,long options);
+long unix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long unix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+int unix_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg);
+int unix_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set);
+
+void unix_abort (MAILSTREAM *stream);
+char *unix_file (char *dst,char *name);
+int unix_lock (char *file,int flags,int mode,DOTLOCK *lock,int op);
+void unix_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock);
+int unix_parse (MAILSTREAM *stream,DOTLOCK *lock,int op);
+char *unix_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size);
+unsigned long unix_pseudo (MAILSTREAM *stream,char *hdr);
+unsigned long unix_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
+ unsigned long uid,long flag);
+long unix_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock,
+ long flags);
+long unix_extend (MAILSTREAM *stream,unsigned long size);
+void unix_write (UNIXFILE *f,char *s,unsigned long i);
+void unix_phys_write (UNIXFILE *f,char *buf,size_t size);
+
+/* mbox mail routines */
+
+/* Function prototypes */
+
+DRIVER *mbox_valid (char *name);
+long mbox_create (MAILSTREAM *stream,char *mailbox);
+long mbox_delete (MAILSTREAM *stream,char *mailbox);
+long mbox_rename (MAILSTREAM *stream,char *old,char *newname);
+long mbox_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *mbox_open (MAILSTREAM *stream);
+long mbox_ping (MAILSTREAM *stream);
+void mbox_check (MAILSTREAM *stream);
+long mbox_expunge (MAILSTREAM *stream,char *sequence,long options);
+long mbox_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+
+/* UNIX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER unixdriver = {
+ "unix", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_LOCKING|DR_NONEWMAILRONLY|DR_XPOINT,
+ (DRIVER *) NIL, /* next driver */
+ unix_valid, /* mailbox is valid for us */
+ unix_parameters, /* manipulate parameters */
+ unix_scan, /* scan mailboxes */
+ unix_list, /* list mailboxes */
+ unix_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ unix_create, /* create mailbox */
+ unix_delete, /* delete mailbox */
+ unix_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ unix_open, /* open mailbox */
+ unix_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ unix_header, /* fetch message header */
+ unix_text, /* fetch message text */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ unix_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ unix_ping, /* ping mailbox to see if still alive */
+ unix_check, /* check for new messages */
+ unix_expunge, /* expunge deleted messages */
+ unix_copy, /* copy messages to another mailbox */
+ unix_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM unixproto = {&unixdriver};
+
+ /* driver parameters */
+static long unix_fromwidget = T;
+
+/* UNIX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *unix_valid (char *name)
+{
+ int fd;
+ DRIVER *ret = NIL;
+ char *t,file[MAILTMPLEN];
+ struct stat sbuf;
+ time_t tp[2];
+ errno = EINVAL; /* assume invalid argument */
+ /* must be non-empty file */
+ if ((t = dummy_file (file,name)) && !stat (t,&sbuf)) {
+ if (!sbuf.st_size)errno = 0;/* empty file */
+ else if ((fd = open (file,O_RDONLY,NIL)) >= 0) {
+ /* OK if mailbox format good */
+ if (unix_isvalid_fd (fd)) ret = &unixdriver;
+ else errno = -1; /* invalid format */
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) {
+ tp[0] = sbuf.st_atime; /* yes, preserve atime and mtime */
+ tp[1] = sbuf.st_mtime;
+ utime (file,tp); /* set the times */
+ }
+ }
+ }
+ return ret; /* return what we should */
+}
+
+/* UNIX mail test for valid mailbox
+ * Accepts: file descriptor
+ * scratch buffer
+ * Returns: T if valid, NIL otherwise
+ */
+
+long unix_isvalid_fd (int fd)
+{
+ int zn;
+ int ret = NIL;
+ char tmp[MAILTMPLEN],*s,*t,c = '\n';
+ memset (tmp,'\0',MAILTMPLEN);
+ if (read (fd,tmp,MAILTMPLEN-1) >= 0) {
+ for (s = tmp; (*s == '\r') || (*s == '\n') || (*s == ' ') || (*s == '\t');)
+ c = *s++;
+ if (c == '\n') VALID (s,t,ret,zn);
+ }
+ return ret; /* return what we should */
+}
+
+
+/* UNIX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *unix_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = dummy_file ((char *) value,"INBOX");
+ break;
+ case SET_FROMWIDGET:
+ unix_fromwidget = (long) value;
+ case GET_FROMWIDGET:
+ ret = (void *) unix_fromwidget;
+ break;
+ }
+ return ret;
+}
+
+/* UNIX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void unix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* UNIX mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void unix_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* UNIX mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void unix_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* UNIX mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long unix_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN];
+ long ret = NIL;
+ int i,fd;
+ time_t ti = time (0);
+ if (!(s = dummy_file (mbx,mailbox))) {
+ sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
+ MM_LOG (tmp,ERROR);
+ }
+ /* create underlying file */
+ else if (dummy_create_path (stream,s,get_dir_protection (mailbox))) {
+ /* done if dir-only or whiner */
+ if (((s = strrchr (s,'/')) && !s[1]) ||
+ mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) ret = T;
+ else if ((fd = open (mbx,O_WRONLY,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0) {
+ sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else { /* initialize header */
+ memset (tmp,'\0',MAILTMPLEN);
+ sprintf (tmp,"From %s %sDate: ",pseudo_from,ctime (&ti));
+ rfc822_fixed_date (s = tmp + strlen (tmp));
+ /* write the pseudo-header */
+ sprintf (s += strlen (s),
+ "\nFrom: %s <%s@%s>\nSubject: %s\nX-IMAP: %010lu 0000000000",
+ pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
+ (unsigned long) ti);
+ for (i = 0; i < NUSERFLAGS; ++i) if (default_user_flag (i))
+ sprintf (s += strlen (s)," %s",default_user_flag (i));
+ sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n\n",pseudo_msg);
+ if (write (fd,tmp,strlen (tmp)) > 0) ret = T;
+ else {
+ sprintf (tmp,"Can't initialize mailbox node %.80s: %s",mbx,
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ close (fd); /* close file */
+ }
+ }
+ /* set proper protections */
+ return ret ? set_mbx_protections (mailbox,mbx) : NIL;
+}
+
+/* UNIX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long unix_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return unix_rename (stream,mailbox,NIL);
+}
+
+
+/* UNIX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long unix_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = NIL;
+ char c,*s = NIL;
+ char tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ DOTLOCK lockx;
+ int fd,ld;
+ long i;
+ struct stat sbuf;
+ MM_CRITICAL (stream); /* get the c-client lock */
+ if (!dummy_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'/')) && !s[1]))))
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ /* lock out other c-clients */
+ else if ((ld = lockname (lock,file,LOCK_EX|LOCK_NB,&i)) < 0)
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+
+ else {
+ if ((fd = unix_lock (file,O_RDWR,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lockx,LOCK_EX)) < 0)
+ sprintf (tmp,"Can't lock mailbox %.80s: %s",old,strerror (errno));
+ else {
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if (s = strrchr (s,'/')) {
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp,get_dir_protection (newname))) {
+ unix_unlock (fd,NIL,&lockx);
+ unix_unlock (ld,NIL,NIL);
+ unlink (lock);
+ MM_NOCRITICAL (stream);
+ return ret; /* return success or failure */
+ }
+ *s = c; /* restore full name */
+ }
+ if (rename (file,tmp))
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ else ret = T; /* set success */
+ }
+ else if (unlink (file))
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ else ret = T; /* set success */
+ unix_unlock (fd,NIL,&lockx);
+ }
+ unix_unlock (ld,NIL,NIL); /* flush the lock */
+ unlink (lock);
+ }
+ MM_NOCRITICAL (stream); /* no longer critical */
+ if (!ret) MM_LOG (tmp,ERROR); /* log error */
+ return ret; /* return success or failure */
+}
+
+/* UNIX mail open
+ * Accepts: Stream to open
+ * Returns: Stream on success, NIL on failure
+ */
+
+MAILSTREAM *unix_open (MAILSTREAM *stream)
+{
+ long i;
+ int fd;
+ char tmp[MAILTMPLEN];
+ DOTLOCK lock;
+ long retry;
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&unixproto);
+ retry = stream->silent ? 1 : KODRETRY;
+ if (stream->local) fatal ("unix recycle stream");
+ stream->local = memset (fs_get (sizeof (UNIXLOCAL)),0,sizeof (UNIXLOCAL));
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ /* canonicalize the stream mailbox name */
+ if (!dummy_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* flush old name */
+ fs_give ((void **) &stream->mailbox);
+ /* save canonical name */
+ stream->mailbox = cpystr (tmp);
+ LOCAL->fd = LOCAL->ld = -1; /* no file or state locking yet */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE);
+ LOCAL->text.size = CHUNKSIZE - 1;
+ LOCAL->linebuf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->linebuflen = CHUNKSIZE - 1;
+ stream->sequence++; /* bump sequence number */
+
+ /* make lock for read/write access */
+ if (!stream->rdonly) while (retry) {
+ /* try to lock file */
+ if ((fd = lockname (tmp,stream->mailbox,LOCK_EX|LOCK_NB,&i)) < 0) {
+ /* suppressing kiss-of-death? */
+ if (stream->nokod) retry = 0;
+ /* no, first time through? */
+ else if (retry-- == KODRETRY) {
+ /* learned other guy's PID and can signal? */
+ if (i && !kill ((int) i,SIGUSR2)) {
+ sprintf (tmp,"Trying to get mailbox lock from process %ld",i);
+ MM_LOG (tmp,WARN);
+ }
+ else retry = 0; /* give up */
+ }
+ if (!stream->silent) { /* nothing if silent stream */
+ if (retry) sleep (1); /* wait a second before trying again */
+ else MM_LOG ("Mailbox is open by another process, access is readonly",
+ WARN);
+ }
+ }
+ else { /* got the lock, nobody else can alter state */
+ LOCAL->ld = fd; /* note lock's fd and name */
+ LOCAL->lname = cpystr (tmp);
+ /* make sure mode OK (don't use fchmod()) */
+ chmod (LOCAL->lname,(long) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
+ if (stream->silent) i = 0;/* silent streams won't accept KOD */
+ else { /* note our PID in the lock */
+ sprintf (tmp,"%d",getpid ());
+ write (fd,tmp,(i = strlen (tmp))+1);
+ }
+ ftruncate (fd,i); /* make sure tied off */
+ fsync (fd); /* make sure it's available */
+ retry = 0; /* no more need to try */
+ }
+ }
+
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ /* will we be able to get write access? */
+ if ((LOCAL->ld >= 0) && access (stream->mailbox,W_OK) && (errno == EACCES)) {
+ MM_LOG ("Can't get write access to mailbox, access is readonly",WARN);
+ flock (LOCAL->ld,LOCK_UN); /* release the lock */
+ close (LOCAL->ld); /* close the lock file */
+ LOCAL->ld = -1; /* no more lock fd */
+ unlink (LOCAL->lname); /* delete it */
+ }
+ /* reset UID validity */
+ stream->uid_validity = stream->uid_last = 0;
+ if (stream->silent && !stream->rdonly && (LOCAL->ld < 0))
+ unix_abort (stream); /* abort if can't get RW silent stream */
+ /* parse mailbox */
+ else if (unix_parse (stream,&lock,LOCK_SH)) {
+ unix_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream); /* done with critical */
+ }
+ if (!LOCAL) return NIL; /* failure if stream died */
+ /* make sure upper level knows readonly */
+ stream->rdonly = (LOCAL->ld < 0);
+ /* notify about empty mailbox */
+ if (!(stream->nmsgs || stream->silent)) MM_LOG ("Mailbox is empty",NIL);
+ if (!stream->rdonly) { /* flags stick if readwrite */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft = T;
+ if (!stream->uid_nosticky) {/* users with lives get permanent keywords */
+ stream->perm_user_flags = 0xffffffff;
+ /* and maybe can create them too! */
+ stream->kwd_create = stream->user_flags[NUSERFLAGS-1] ? NIL : T;
+ }
+ }
+ return stream; /* return stream alive to caller */
+}
+
+
+/* UNIX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void unix_close (MAILSTREAM *stream,long options)
+{
+ int silent = stream->silent;
+ stream->silent = T; /* go silent */
+ /* expunge if requested */
+ if (options & CL_EXPUNGE) unix_expunge (stream,NIL,NIL);
+ /* else dump final checkpoint */
+ else if (LOCAL->dirty) unix_check (stream);
+ stream->silent = silent; /* restore old silence state */
+ unix_abort (stream); /* now punt the file and local data */
+}
+
+/* UNIX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+ /* lines to filter from header */
+static STRINGLIST *unix_hlines = NIL;
+
+char *unix_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned char *s,*t,*tl;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get cache */
+ if (!unix_hlines) { /* once only code */
+ STRINGLIST *lines = unix_hlines = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "Status"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-Status"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-Keywords"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-UID"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-IMAP"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-IMAPbase"));
+ }
+ /* go to header position */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.msg.header.offset,L_SET);
+
+ if (flags & FT_INTERNAL) { /* initial data OK? */
+ if (elt->private.msg.header.text.size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
+ elt->private.msg.header.text.size) + 1);
+ }
+ /* read message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.header.text.size);
+ /* got text, tie off string */
+ LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0';
+ /* squeeze out CRs (in case from PC) */
+ for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
+ if (*t != '\r') *s++ = *t;
+ *s = '\0';
+ *length = s - LOCAL->buf; /* adjust length */
+ }
+ else { /* need to make a CRLF version */
+ read (LOCAL->fd,s = (char *) fs_get (elt->private.msg.header.text.size+1),
+ elt->private.msg.header.text.size);
+ /* tie off string, and convert to CRLF */
+ s[elt->private.msg.header.text.size] = '\0';
+ *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s,
+ elt->private.msg.header.text.size);
+ fs_give ((void **) &s); /* free readin buffer */
+ /* squeeze out spurious CRs */
+ for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
+ if ((*t != '\r') || (t[1] == '\n')) *s++ = *t;
+ *s = '\0';
+ *length = s - LOCAL->buf; /* adjust length */
+ }
+ *length = mail_filter (LOCAL->buf,*length,unix_hlines,FT_NOT);
+ return (char *) LOCAL->buf; /* return processed copy */
+}
+
+/* UNIX mail fetch message text
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL if failure
+ */
+
+long unix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ char *s;
+ unsigned long i;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);/* get cache element */
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ /* mark message seen and dirty */
+ elt->seen = elt->private.dirty = LOCAL->dirty = T;
+ MM_FLAGS (stream,msgno);
+ }
+ s = unix_text_work (stream,elt,&i,flags);
+ INIT (bs,mail_string,s,i); /* set up stringstruct */
+ return T; /* success */
+}
+
+/* UNIX mail fetch message text worker routine
+ * Accepts: MAIL stream
+ * message cache element
+ * pointer to returned header text length
+ * option flags
+ */
+
+char *unix_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
+ unsigned long *length,long flags)
+{
+ FDDATA d;
+ STRING bs;
+ unsigned char c,*s,*t,*tl,tmp[CHUNKSIZE];
+ /* go to text position */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.msg.text.offset,L_SET);
+ if (flags & FT_INTERNAL) { /* initial data OK? */
+ if (elt->private.msg.text.text.size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
+ elt->private.msg.text.text.size) + 1);
+ }
+ /* read message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size);
+ /* got text, tie off string */
+ LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0';
+ /* squeeze out CRs (in case from PC) */
+ for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
+ if (*t != '\r') *s++ = *t;
+ *s = '\0';
+ *length = s - LOCAL->buf; /* adjust length */
+ return (char *) LOCAL->buf;
+ }
+
+ /* have it cached already? */
+ if (elt->private.uid != LOCAL->uid) {
+ /* not cached, cache it now */
+ LOCAL->uid = elt->private.uid;
+ /* is buffer big enough? */
+ if (elt->rfc822_size > LOCAL->text.size) {
+ /* excessively conservative, but the right thing is too hard to do */
+ fs_give ((void **) &LOCAL->text.data);
+ LOCAL->text.data = (unsigned char *)
+ fs_get ((LOCAL->text.size = elt->rfc822_size) + 1);
+ }
+ d.fd = LOCAL->fd; /* yes, set up file descriptor */
+ d.pos = elt->private.special.offset + elt->private.msg.text.offset;
+ d.chunk = tmp; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* file chunk size */
+ INIT (&bs,fd_string,&d,elt->private.msg.text.text.size);
+ for (s = (char *) LOCAL->text.data; SIZE (&bs);) switch (c = SNX (&bs)) {
+ case '\r': /* carriage return seen */
+ break;
+ case '\n':
+ *s++ = '\r'; /* insert a CR */
+ default:
+ *s++ = c; /* copy characters */
+ }
+ *s = '\0'; /* tie off buffer */
+ /* calculate length of cached data */
+ LOCAL->textlen = s - LOCAL->text.data;
+ }
+ *length = LOCAL->textlen; /* return from cache */
+ return (char *) LOCAL->text.data;
+}
+
+/* UNIX per-message modify flag
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void unix_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ /* only after finishing */
+ if (elt->valid) elt->private.dirty = LOCAL->dirty = T;
+}
+
+
+/* UNIX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long unix_ping (MAILSTREAM *stream)
+{
+ DOTLOCK lock;
+ struct stat sbuf;
+ long reparse;
+ /* big no-op if not readwrite */
+ if (LOCAL && (LOCAL->ld >= 0) && !stream->lock) {
+ if (stream->rdonly) { /* does he want to give up readwrite? */
+ /* checkpoint if we changed something */
+ if (LOCAL->dirty) unix_check (stream);
+ flock (LOCAL->ld,LOCK_UN);/* release readwrite lock */
+ close (LOCAL->ld); /* close the readwrite lock file */
+ LOCAL->ld = -1; /* no more readwrite lock fd */
+ unlink (LOCAL->lname); /* delete the readwrite lock file */
+ }
+ else { /* see if need to reparse */
+ if (!(reparse = (long) mail_parameters (NIL,GET_NETFSSTATBUG,NIL))) {
+ /* get current mailbox size */
+ if (LOCAL->fd >= 0) fstat (LOCAL->fd,&sbuf);
+ else if (stat (stream->mailbox,&sbuf)) {
+ sprintf (LOCAL->buf,"Mailbox stat failed, aborted: %s",
+ strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ unix_abort (stream);
+ return NIL;
+ }
+ reparse = (sbuf.st_size != LOCAL->filesize);
+ }
+ /* parse if mailbox changed */
+ if ((LOCAL->ddirty || reparse) && unix_parse (stream,&lock,LOCK_EX)) {
+ /* force checkpoint if double-dirty */
+ if (LOCAL->ddirty) unix_rewrite (stream,NIL,&lock,NIL);
+ /* unlock mailbox */
+ else unix_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* and stream */
+ MM_NOCRITICAL (stream); /* done with critical */
+ }
+ }
+ }
+ return LOCAL ? LONGT : NIL; /* return if still alive */
+}
+
+/* UNIX mail check mailbox
+ * Accepts: MAIL stream
+ */
+
+void unix_check (MAILSTREAM *stream)
+{
+ DOTLOCK lock;
+ /* parse and lock mailbox */
+ if (LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
+ unix_parse (stream,&lock,LOCK_EX)) {
+ /* any unsaved changes? */
+ if (LOCAL->dirty && unix_rewrite (stream,NIL,&lock,NIL)) {
+ if (!stream->silent) MM_LOG ("Checkpoint completed",NIL);
+ }
+ /* no checkpoint needed, just unlock */
+ else unix_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* unlock the stream */
+ MM_NOCRITICAL (stream); /* done with critical */
+ }
+}
+
+
+/* UNIX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long unix_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ unsigned long i;
+ DOTLOCK lock;
+ char *msg = NIL;
+ /* parse and lock mailbox */
+ if (ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
+ unix_parse (stream,&lock,LOCK_EX)) {
+ /* check expunged messages if not dirty */
+ for (i = 1; !LOCAL->dirty && (i <= stream->nmsgs); i++) {
+ MESSAGECACHE *elt = mail_elt (stream,i);
+ if (mail_elt (stream,i)->deleted) LOCAL->dirty = T;
+ }
+ if (!LOCAL->dirty) { /* not dirty and no expunged messages */
+ unix_unlock (LOCAL->fd,stream,&lock);
+ msg = "No messages deleted, so no update needed";
+ }
+ else if (unix_rewrite (stream,&i,&lock,sequence ? LONGT : NIL)) {
+ if (i) sprintf (msg = LOCAL->buf,"Expunged %lu messages",i);
+ else msg = "Mailbox checkpointed, but no messages expunged";
+ }
+ /* rewrite failed */
+ else unix_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* unlock the stream */
+ MM_NOCRITICAL (stream); /* done with critical */
+ if (msg && !stream->silent) MM_LOG (msg,NIL);
+ }
+ else if (!stream->silent) MM_LOG("Expunge ignored on readonly mailbox",WARN);
+ return ret;
+}
+
+/* UNIX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long unix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ int fd;
+ char *s,file[MAILTMPLEN];
+ DOTLOCK lock;
+ time_t tp[2];
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ long ret = T;
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ copyuid_t cu = (copyuid_t) (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ?
+ NIL : mail_parameters (NIL,GET_COPYUID,NIL));
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ MAILSTREAM *tstream = NIL;
+ DRIVER *d;
+ for (d = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL);
+ (d && strcmp (d->name,"mbox") && !(d->flags & DR_DISABLE));
+ d = d->next); /* see if mbox driver active */
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* make sure destination is valid */
+ if (!((d && mbox_valid (mailbox) && (mailbox = "mbox")) ||
+ unix_valid (mailbox) || !errno))
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ }
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ unix_create (NIL,"INBOX");/* create empty INBOX */
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid UNIX-format mailbox name: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a UNIX-format mailbox: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+
+ /* try to open rewrite for UIDPLUS */
+ if ((tstream = mail_open_work (&unixdriver,NIL,mailbox,
+ OP_SILENT|OP_NOKOD)) && tstream->rdonly)
+ tstream = mail_close (tstream);
+ if (cu && !tstream) { /* wanted a COPYUID? */
+ sprintf (LOCAL->buf,"Unable to write-open mailbox for COPYUID: %.80s",
+ mailbox);
+ MM_LOG (LOCAL->buf,WARN);
+ cu = NIL; /* don't try to do COPYUID */
+ }
+ LOCAL->buf[0] = '\0';
+ MM_CRITICAL (stream); /* go critical */
+ if ((fd = unix_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lock,LOCK_EX)) < 0) {
+ MM_NOCRITICAL (stream); /* done with critical */
+ sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);/* log the error */
+ return NIL; /* failed */
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ /* write all requested messages to mailbox */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
+ if (write (fd,LOCAL->buf,elt->private.special.text.size) < 0) ret = NIL;
+ else { /* internal header succeeded */
+ s = unix_header (stream,i,&j,FT_INTERNAL);
+ /* header size, sans trailing newline */
+ if (j && (s[j - 2] == '\n')) j--;
+ if (write (fd,s,j) < 0) ret = NIL;
+ else { /* message header succeeded */
+ j = tstream ? /* write UIDPLUS data if have readwrite */
+ unix_xstatus (stream,LOCAL->buf,elt,++(tstream->uid_last),LONGT) :
+ unix_xstatus (stream,LOCAL->buf,elt,NIL,NIL);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ else { /* message status succeeded */
+ s = unix_text_work (stream,elt,&j,FT_INTERNAL);
+ if ((write (fd,s,j) < 0) || (write (fd,"\n",1) < 0)) ret = NIL;
+ else if (cu) { /* need to pass back new UID? */
+ mail_append_set (source,mail_uid (stream,i));
+ mail_append_set (dest,tstream->uid_last);
+ }
+ }
+ }
+ }
+ }
+
+ if (!ret || fsync (fd)) { /* force out the update */
+ sprintf (LOCAL->buf,"Message copy failed: %s",strerror (errno));
+ ftruncate (fd,sbuf.st_size);
+ ret = NIL;
+ }
+ /* force UIDVALIDITY assignment now */
+ if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0);
+ /* return sets if doing COPYUID */
+ if (cu && ret) (*cu) (stream,mailbox,tstream->uid_validity,source,dest);
+ else { /* flush any sets we may have built */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ tp[1] = time (0); /* set mtime to now */
+ if (ret) tp[0] = tp[1] - 1; /* set atime to now-1 if successful copy */
+ else tp[0] = /* else preserve \Marked status */
+ ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
+ sbuf.st_atime : tp[1];
+ utime (file,tp); /* set the times */
+ unix_unlock (fd,NIL,&lock); /* unlock and close mailbox */
+ if (tstream) { /* update last UID if we can */
+ UNIXLOCAL *local = (UNIXLOCAL *) tstream->local;
+ local->dirty = T; /* do a rewrite */
+ local->appending = T; /* but not at the cost of marking as old */
+ tstream = mail_close (tstream);
+ }
+ /* log the error */
+ if (!ret) MM_LOG (LOCAL->buf,ERROR);
+ /* delete if requested message */
+ else if (options & CP_MOVE) for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence)
+ elt->deleted = elt->private.dirty = LOCAL->dirty = T;
+ MM_NOCRITICAL (stream); /* release critical */
+ return ret;
+}
+
+/* UNIX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+#define BUFLEN 8*MAILTMPLEN
+
+long unix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd;
+ unsigned long i;
+ char *flags,*date,buf[BUFLEN],tmp[MAILTMPLEN],file[MAILTMPLEN];
+ time_t tp[2];
+ FILE *sf,*df;
+ MESSAGECACHE elt;
+ DOTLOCK lock;
+ STRING *message;
+ unsigned long uidlocation = 0;
+ appenduid_t au = (appenduid_t)
+ (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ? NIL :
+ mail_parameters (NIL,GET_APPENDUID,NIL));
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ long ret = LONGT;
+ MAILSTREAM *tstream = NIL;
+ if (!stream) { /* stream specified? */
+ stream = &unixproto; /* no, default stream to prototype */
+ for (i = 0; i < NUSERFLAGS && stream->user_flags[i]; ++i)
+ fs_give ((void **) &stream->user_flags[i]);
+ }
+ if (!unix_valid (mailbox)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ unix_create (NIL,"INBOX"); /* create empty INBOX */
+ case 0: /* merely empty file? */
+ tstream = stream;
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid UNIX-format mailbox name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a UNIX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get sniffing stream for keywords */
+ else if (!(tstream = mail_open (NIL,mailbox,
+ OP_READONLY|OP_SILENT|OP_NOKOD|OP_SNIFF))) {
+ sprintf (tmp,"Unable to examine mailbox for APPEND: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+
+ /* get first message */
+ if (!MM_APPEND (af) (tstream,data,&flags,&date,&message)) return NIL;
+ if (!(sf = tmpfile ())) { /* must have scratch file */
+ sprintf (tmp,".%lx.%lx",(unsigned long) time (0),(unsigned long)getpid ());
+ if (!stat (tmp,&sbuf) || !(sf = fopen (tmp,"wb+"))) {
+ sprintf (tmp,"Unable to create scratch file: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ unlink (tmp);
+ }
+ do { /* parse date */
+ if (!date) rfc822_date (date = tmp);
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ }
+ else { /* user wants to suppress time zones? */
+ if (mail_parameters (NIL,GET_NOTIMEZONES,NIL)) {
+ time_t when = mail_longdate (&elt);
+ date = ctime (&when); /* use traditional date */
+ }
+ /* use POSIX-style date */
+ else date = mail_cdate (tmp,&elt);
+ if (!SIZE (message)) MM_LOG ("Append of zero-length message",ERROR);
+ else if (!unix_collect_msg (tstream,sf,flags,date,message)) {
+ sprintf (tmp,"Error writing scratch file: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ /* get next message */
+ else if (MM_APPEND (af) (tstream,data,&flags,&date,&message)) continue;
+ }
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ } while (message); /* until no more messages */
+ if (fflush (sf)) {
+ sprintf (tmp,"Error finishing scratch file: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ }
+ i = ftell (sf); /* size of scratch file */
+ /* close sniffing stream */
+ if (tstream != stream) tstream = mail_close (tstream);
+
+ MM_CRITICAL (stream); /* go critical */
+ /* try to open readwrite for UIDPLUS */
+ if ((tstream = mail_open_work (&unixdriver,NIL,mailbox,
+ OP_SILENT|OP_NOKOD)) && tstream->rdonly)
+ tstream = mail_close (tstream);
+ if (au && !tstream) { /* wanted an APPENDUID? */
+ sprintf (tmp,"Unable to re-open mailbox for APPENDUID: %.80s",mailbox);
+ MM_LOG (tmp,WARN);
+ au = NIL;
+ }
+ if (((fd = unix_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lock,LOCK_EX)) < 0) ||
+ !(df = fdopen (fd,"ab"))) {
+ MM_NOCRITICAL (stream); /* done with critical */
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ rewind (sf);
+ tp[1] = time (0); /* set mtime to now */
+ /* write all messages */
+ if (!unix_append_msgs (tstream,sf,df,au ? dst : NIL) ||
+ (fflush (df) == EOF) || fsync (fd)) {
+ sprintf (buf,"Message append failed: %s",strerror (errno));
+ MM_LOG (buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ tp[0] = /* preserve \Marked status */
+ ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
+ sbuf.st_atime : tp[1];
+ ret = NIL; /* return error */
+ }
+ else tp[0] = tp[1] - 1; /* set atime to now-1 if successful copy */
+ utime (file,tp); /* set the times */
+ fclose (sf); /* done with scratch file */
+ /* force UIDVALIDITY assignment now */
+ if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0);
+ /* return sets if doing APPENDUID */
+ if (au && ret) (*au) (mailbox,tstream->uid_validity,dst);
+ else mail_free_searchset (&dst);
+ unix_unlock (fd,NIL,&lock); /* unlock and close mailbox */
+ fclose (df); /* note that unix_unlock() released the fd */
+ if (tstream) { /* update last UID if we can */
+ UNIXLOCAL *local = (UNIXLOCAL *) tstream->local;
+ local->dirty = T; /* do a rewrite */
+ local->appending = T; /* but not at the cost of marking as old */
+ tstream = mail_close (tstream);
+ }
+ MM_NOCRITICAL (stream); /* release critical */
+ return ret;
+}
+
+/* Collect and write single message to append scratch file
+ * Accepts: MAIL stream
+ * scratch file
+ * flags
+ * date
+ * message stringstruct
+ * Returns: NIL if write error, else T
+ */
+
+int unix_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg)
+{
+ unsigned char *s,*t;
+ unsigned long uf;
+ long f = mail_parse_flags (stream,flags,&uf);
+ /* write metadata, note date ends with NL */
+ if (fprintf (sf,"%ld %lu %s",f,SIZE (msg) + 1,date) < 0) return NIL;
+ while (uf) /* write user flags */
+ if ((s = stream->user_flags[find_rightmost_bit (&uf)]) &&
+ (fprintf (sf," %s",s) < 0)) return NIL;
+ if (putc ('\n',sf) == EOF) return NIL;
+ while (SIZE (msg)) { /* copy text to scratch file */
+ for (s = (unsigned char *) msg->curpos, t = s + msg->cursize; s < t; ++s)
+ if (!*s) *s = 0x80; /* disallow NUL */
+ /* write buffered text */
+ if (fwrite (msg->curpos,1,msg->cursize,sf) == msg->cursize)
+ SETPOS (msg,GETPOS (msg) + msg->cursize);
+ else return NIL; /* failed */
+ }
+ /* write trailing newline and return */
+ return (putc ('\n',sf) == EOF) ? NIL : T;
+}
+
+/* Append messages from scratch file to mailbox
+ * Accepts: MAIL stream
+ * source file
+ * destination file
+ * uidset to update if non-NIL
+ * Returns: T if success, NIL if failure
+ */
+
+int unix_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set)
+{
+ int ti,zn,c;
+ long f;
+ unsigned long i,j;
+ char *x,tmp[MAILTMPLEN];
+ int hdrp = T;
+ /* get message metadata line */
+ while (fgets (tmp,MAILTMPLEN,sf)) {
+ if (!(isdigit (tmp[0]) && strchr (tmp,'\n'))) return NIL;
+ f = strtol (tmp,&x,10); /* get flags */
+ if (!((*x++ == ' ') && isdigit (*x))) return NIL;
+ i = strtoul (x,&x,10); /* get message size */
+ if ((*x++ != ' ') || /* build initial header */
+ (fprintf (df,"From %s@%s %sStatus: ",myusername(),mylocalhost(),x)<0)||
+ (f&fSEEN && (putc ('R',df) == EOF)) ||
+ (fputs ("\nX-Status: ",df) == EOF) ||
+ (f&fDELETED && (putc ('D',df) == EOF)) ||
+ (f&fFLAGGED && (putc ('F',df) == EOF)) ||
+ (f&fANSWERED && (putc ('A',df) == EOF)) ||
+ (f&fDRAFT && (putc ('T',df) == EOF)) ||
+ (fputs ("\nX-Keywords:",df) == EOF)) return NIL;
+ /* copy keywords */
+ while ((c = getc (sf)) != '\n') switch (c) {
+ case EOF:
+ return NIL;
+ default:
+ if (putc (c,df) == EOF) return NIL;
+ }
+ if ((putc ('\n',df) == EOF) ||
+ (set && (fprintf (df,"X-UID: %lu\n",++(stream->uid_last)) < 0)))
+ return NIL;
+
+ for (c = '\n'; i && fgets (tmp,MAILTMPLEN,sf); c = tmp[j-1]) {
+ /* get read line length */
+ if (i < (j = strlen (tmp))) fatal ("unix_append_msgs overrun");
+ i -= j; /* number of bytes left */
+ /* squish out CRs (note also copies NUL) */
+ for (x = tmp; x = strchr (x,'\r'); --j) memmove (x,x+1,j-(x-tmp));
+ if (!j) continue; /* do nothing if line emptied */
+ /* start of line? */
+ if ((c == '\n')) switch (tmp[0]) {
+ case 'F': /* possible "From " (case counts here) */
+ if ((j > 4) && (tmp[0] == 'F') && (tmp[1] == 'r') && (tmp[2] == 'o') &&
+ (tmp[3] == 'm') && (tmp[4] == ' ')) {
+ if (!unix_fromwidget) {
+ VALID (tmp,x,ti,zn);/* conditional, only write widget if */
+ if (!ti) break; /* it looks like a valid header */
+ } /* write the widget */
+ if (putc ('>',df) == EOF) return NIL;
+ }
+ break;
+ case 'S': case 's': /* possible "Status:" */
+ if (hdrp && (j > 6) && ((tmp[1] == 't') || (tmp[1] == 'T')) &&
+ ((tmp[2] == 'a') || (tmp[2] == 'A')) &&
+ ((tmp[3] == 't') || (tmp[3] == 'T')) &&
+ ((tmp[4] == 'u') || (tmp[4] == 'U')) &&
+ ((tmp[5] == 's') || (tmp[5] == 'S')) && (tmp[6] == ':') &&
+ (fputs ("X-Original-",df) == EOF)) return NIL;
+ break;
+ case 'X': case 'x': /* possible X-??? header */
+ if (hdrp && (tmp[1] == '-') &&
+ /* possible X-UID: */
+ (((j > 5) && ((tmp[2] == 'U') || (tmp[2] == 'u')) &&
+ ((tmp[3] == 'I') || (tmp[3] == 'i')) &&
+ ((tmp[4] == 'D') || (tmp[4] == 'd')) && (tmp[5] == ':')) ||
+ /* possible X-IMAP: */
+ ((j > 6) && ((tmp[2] == 'I') || (tmp[2] == 'i')) &&
+ ((tmp[3] == 'M') || (tmp[3] == 'm')) &&
+ ((tmp[4] == 'A') || (tmp[4] == 'a')) &&
+ ((tmp[5] == 'P') || (tmp[5] == 'p')) &&
+ ((tmp[6] == ':') ||
+ /* or X-IMAPbase: */
+ ((j > 10) && ((tmp[6] == 'b') || (tmp[6] == 'B')) &&
+ ((tmp[7] == 'a') || (tmp[7] == 'A')) &&
+ ((tmp[8] == 's') || (tmp[8] == 'S')) &&
+ ((tmp[9] == 'e') || (tmp[9] == 'E')) && (tmp[10] == ':')))) ||
+ /* possible X-Status: */
+ ((j > 8) && ((tmp[2] == 'S') || (tmp[2] == 's')) &&
+ ((tmp[3] == 't') || (tmp[3] == 'T')) &&
+ ((tmp[4] == 'a') || (tmp[4] == 'A')) &&
+ ((tmp[5] == 't') || (tmp[5] == 'T')) &&
+ ((tmp[6] == 'u') || (tmp[6] == 'U')) &&
+ ((tmp[7] == 's') || (tmp[7] == 'S')) && (tmp[8] == ':')) ||
+ /* possible X-Keywords: */
+ ((j > 10) && ((tmp[2] == 'K') || (tmp[2] == 'k')) &&
+ ((tmp[3] == 'e') || (tmp[3] == 'E')) &&
+ ((tmp[4] == 'y') || (tmp[4] == 'Y')) &&
+ ((tmp[5] == 'w') || (tmp[5] == 'W')) &&
+ ((tmp[6] == 'o') || (tmp[6] == 'O')) &&
+ ((tmp[7] == 'r') || (tmp[7] == 'R')) &&
+ ((tmp[8] == 'd') || (tmp[8] == 'D')) &&
+ ((tmp[9] == 's') || (tmp[9] == 'S')) && (tmp[10] == ':'))) &&
+ (fputs ("X-Original-",df) == EOF)) return NIL;
+ case '\n': /* blank line */
+ hdrp = NIL;
+ break;
+ default: /* nothing to do */
+ break;
+ }
+ /* just write the line */
+ if (fwrite (tmp,1,j,df) != j) return NIL;
+ }
+ if (i) return NIL; /* didn't read entire message */
+ /* update set */
+ if (stream) mail_append_set (set,stream->uid_last);
+ }
+ return T;
+}
+
+/* Internal routines */
+
+
+/* UNIX mail abort stream
+ * Accepts: MAIL stream
+ */
+
+void unix_abort (MAILSTREAM *stream)
+{
+ if (LOCAL) { /* only if a file is open */
+ if (LOCAL->fd >= 0) close (LOCAL->fd);
+ if (LOCAL->ld >= 0) { /* have a mailbox lock? */
+ flock (LOCAL->ld,LOCK_UN);/* yes, release the lock */
+ close (LOCAL->ld); /* close the lock file */
+ unlink (LOCAL->lname); /* and delete it */
+ }
+ if (LOCAL->lname) fs_give ((void **) &LOCAL->lname);
+ /* free local text buffers */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data);
+ if (LOCAL->linebuf) fs_give ((void **) &LOCAL->linebuf);
+ if (LOCAL->line) fs_give ((void **) &LOCAL->line);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* UNIX open and lock mailbox
+ * Accepts: file name to open/lock
+ * file open mode
+ * destination buffer for lock file name
+ * type of locking operation (LOCK_SH or LOCK_EX)
+ */
+
+int unix_lock (char *file,int flags,int mode,DOTLOCK *lock,int op)
+{
+ int fd;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ (*bn) (BLOCK_FILELOCK,NIL);
+ /* try locking the easy way */
+ if (dotlock_lock (file,lock,-1)) {
+ /* got dotlock file, easy open */
+ if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
+ else dotlock_unlock (lock); /* open failed, free the dotlock */
+ }
+ /* no dot lock file, open file now */
+ else if ((fd = open (file,flags,mode)) >= 0) {
+ /* try paranoid way to make a dot lock file */
+ if (dotlock_lock (file,lock,fd)) {
+ close (fd); /* get fresh fd in case of timing race */
+ if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
+ /* open failed, free the dotlock */
+ else dotlock_unlock (lock);
+ }
+ else flock (fd,op); /* paranoid way failed, just flock() it */
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return fd;
+}
+
+/* UNIX unlock and close mailbox
+ * Accepts: file descriptor
+ * (optional) mailbox stream to check atime/mtime
+ * (optional) lock file name
+ */
+
+void unix_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock)
+{
+ if (stream) { /* need to muck with times? */
+ struct stat sbuf;
+ time_t tp[2];
+ time_t now = time (0);
+ fstat (fd,&sbuf); /* get file times */
+ if (LOCAL->ld >= 0) { /* yes, readwrite session? */
+ tp[0] = now; /* set atime to now */
+ /* set mtime to (now - 1) if necessary */
+ tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
+ }
+ else if (stream->recent) { /* readonly with recent messages */
+ if ((sbuf.st_atime >= sbuf.st_mtime) ||
+ (sbuf.st_atime >= sbuf.st_ctime))
+ /* keep past mtime, whack back atime */
+ tp[0] = (tp[1] = (sbuf.st_mtime < now) ? sbuf.st_mtime : now) - 1;
+ else now = 0; /* no time change needed */
+ }
+ /* readonly with no recent messages */
+ else if ((sbuf.st_atime < sbuf.st_mtime) ||
+ (sbuf.st_atime < sbuf.st_ctime)) {
+ tp[0] = now; /* set atime to now */
+ /* set mtime to (now - 1) if necessary */
+ tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
+ }
+ else now = 0; /* no time change needed */
+ /* set the times, note change */
+ if (now && !utime (stream->mailbox,tp)) LOCAL->filetime = tp[1];
+ }
+ flock (fd,LOCK_UN); /* release flock'ers */
+ if (!stream) close (fd); /* close the file if no stream */
+ dotlock_unlock (lock); /* flush the lock file if any */
+}
+
+/* UNIX mail parse and lock mailbox
+ * Accepts: MAIL stream
+ * space to write lock file name
+ * type of locking operation
+ * Returns: T if parse OK, critical & mailbox is locked shared; NIL if failure
+ */
+
+int unix_parse (MAILSTREAM *stream,DOTLOCK *lock,int op)
+{
+ int zn;
+ unsigned long i,j,k,m;
+ unsigned char c,*s,*t,*u,tmp[MAILTMPLEN],date[30];
+ int ti = 0,retain = T;
+ unsigned long nmsgs = stream->nmsgs;
+ unsigned long prevuid = nmsgs ? mail_elt (stream,nmsgs)->private.uid : 0;
+ unsigned long recent = stream->recent;
+ unsigned long oldnmsgs = stream->nmsgs;
+ short silent = stream->silent;
+ short pseudoseen = NIL;
+ struct stat sbuf;
+ STRING bs;
+ FDDATA d;
+ MESSAGECACHE *elt;
+ mail_lock (stream); /* guard against recursion or pingers */
+ /* toss out previous descriptor */
+ if (LOCAL->fd >= 0) close (LOCAL->fd);
+ MM_CRITICAL (stream); /* open and lock mailbox (shared OK) */
+ if ((LOCAL->fd = unix_lock (stream->mailbox,(LOCAL->ld >= 0) ?
+ O_RDWR : O_RDONLY,
+ (long)mail_parameters(NIL,GET_MBXPROTECTION,NIL),
+ lock,op)) < 0) {
+ sprintf (tmp,"Mailbox open failed, aborted: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unix_abort (stream);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream); /* done with critical */
+ return NIL;
+ }
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* validate change in size */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (tmp,"Mailbox shrank from %lu to %lu bytes, aborted",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR); /* this is pretty bad */
+ unix_unlock (LOCAL->fd,stream,lock);
+ unix_abort (stream);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream); /* done with critical */
+ return NIL;
+ }
+
+ /* new data? */
+ else if (i = sbuf.st_size - LOCAL->filesize) {
+ d.fd = LOCAL->fd; /* yes, set up file descriptor */
+ d.pos = LOCAL->filesize; /* get to that position in the file */
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* file chunk size */
+ INIT (&bs,fd_string,&d,i); /* initialize stringstruct */
+ /* skip leading whitespace for broken MTAs */
+ while (((c = CHR (&bs)) == '\n') || (c == '\r') ||
+ (c == ' ') || (c == '\t')) SNX (&bs);
+ if (SIZE (&bs)) { /* read new data */
+ /* remember internal header position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ s = unix_mbxline (stream,&bs,&i);
+ t = NIL,zn = 0;
+ if (i) VALID (s,t,ti,zn); /* see if valid From line */
+ if (!ti) { /* someone pulled the rug from under us */
+ sprintf (tmp,"Unexpected changes to mailbox (try restarting): %.20s",
+ (char *) s);
+ MM_LOG (tmp,ERROR);
+ unix_unlock (LOCAL->fd,stream,lock);
+ unix_abort (stream);
+ mail_unlock (stream);
+ /* done with critical */
+ MM_NOCRITICAL (stream);
+ return NIL;
+ }
+ stream->silent = T; /* quell main program new message events */
+ do { /* found a message */
+ /* instantiate first new message */
+ mail_exists (stream,++nmsgs);
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ recent++; /* assume recent by default */
+ elt->recent = T;
+ /* note position/size of internal header */
+ elt->private.special.offset = j;
+ elt->private.msg.header.offset = elt->private.special.text.size = i;
+
+ /* generate plausible IMAPish date string */
+ date[2] = date[6] = date[20] = '-'; date[11] = ' ';
+ date[14] = date[17] = ':';
+ /* dd */
+ date[0] = t[ti - 2]; date[1] = t[ti - 1];
+ /* mmm */
+ date[3] = t[ti - 6]; date[4] = t[ti - 5]; date[5] = t[ti - 4];
+ /* hh */
+ date[12] = t[ti + 1]; date[13] = t[ti + 2];
+ /* mm */
+ date[15] = t[ti + 4]; date[16] = t[ti + 5];
+ if (t[ti += 6] == ':') {/* ss */
+ date[18] = t[++ti]; date[19] = t[++ti];
+ ti++; /* move to space */
+ }
+ else date[18] = date[19] = '0';
+ /* yy -- advance over timezone if necessary */
+ if (zn == ti) ti += (((t[zn+1] == '+') || (t[zn+1] == '-')) ? 6 : 4);
+ date[7] = t[ti + 1]; date[8] = t[ti + 2];
+ date[9] = t[ti + 3]; date[10] = t[ti + 4];
+ /* zzz */
+ t = zn ? (t + zn + 1) : (unsigned char *) "LCL";
+ date[21] = *t++; date[22] = *t++; date[23] = *t++;
+ if ((date[21] != '+') && (date[21] != '-')) date[24] = '\0';
+ else { /* numeric time zone */
+ date[24] = *t++; date[25] = *t++;
+ date[26] = '\0'; date[20] = ' ';
+ }
+ /* set internal date */
+ if (!mail_parse_date (elt,date)) {
+ sprintf (tmp,"Unable to parse internal date: %s",(char *) date);
+ MM_LOG (tmp,WARN);
+ }
+
+ do { /* look for message body */
+ s = t = unix_mbxline (stream,&bs,&i);
+ if (i) switch (*s) { /* check header lines */
+ case 'X': /* possible X-???: line */
+ if (s[1] == '-') { /* must be immediately followed by hyphen */
+ /* X-Status: becomes Status: in S case */
+ if (s[2] == 'S' && s[3] == 't' && s[4] == 'a' && s[5] == 't' &&
+ s[6] == 'u' && s[7] == 's' && s[8] == ':') s += 2;
+ /* possible X-Keywords */
+ else if (s[2] == 'K' && s[3] == 'e' && s[4] == 'y' &&
+ s[5] == 'w' && s[6] == 'o' && s[7] == 'r' &&
+ s[8] == 'd' && s[9] == 's' && s[10] == ':') {
+ SIZEDTEXT uf;
+ retain = NIL; /* don't retain continuation */
+ s += 11; /* flush leading whitespace */
+ while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n'))){
+ while (*s == ' ') s++;
+ /* find end of keyword */
+ if (!(u = strpbrk (s," \n\r"))) u = s + strlen (s);
+ /* got a keyword? */
+ if ((k = (u - s)) && (k <= MAXUSERFLAG)) {
+ uf.data = (unsigned char *) s;
+ uf.size = k;
+ for (j = 0; (j < NUSERFLAGS) && stream->user_flags[j]; ++j)
+ if (!compare_csizedtext (stream->user_flags[j],&uf)) {
+ elt->user_flags |= ((long) 1) << j;
+ break;
+ }
+ }
+ s = u; /* advance to next keyword */
+ }
+ break;
+ }
+
+ /* possible X-IMAP */
+ else if ((s[2] == 'I') && (s[3] == 'M') && (s[4] == 'A') &&
+ (s[5] == 'P') && ((m = (s[6] == ':')) ||
+ ((s[6] == 'b') && (s[7] == 'a') &&
+ (s[8] == 's') && (s[9] == 'e') &&
+ (s[10] == ':')))) {
+ retain = NIL; /* don't retain continuation */
+ if ((nmsgs == 1) && !stream->uid_validity) {
+ /* advance to data */
+ s += m ? 7 : 11;
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ j = 0; /* slurp UID validity */
+ /* found a digit? */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ /* must have valid UID validity and UID last */
+ if (j && isdigit (*s)) {
+ /* pseudo-header seen if X-IMAP */
+ if (m) pseudoseen = LOCAL->pseudo = T;
+ /* save UID validity */
+ stream->uid_validity = j;
+ j = 0; /* slurp UID last */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* save UID last */
+ stream->uid_last = j;
+ /* process keywords */
+ for (j = 0; (*s != '\n') && ((*s != '\r')||(s[1] != '\n'));
+ s = u,j++) {
+ /* flush leading whitespace */
+ while (*s == ' ') s++;
+ u = strpbrk (s," \n\r");
+ /* got a keyword? */
+ if ((j < NUSERFLAGS) && (k = (u - s)) &&
+ (k <= MAXUSERFLAG)) {
+ if (stream->user_flags[j])
+ fs_give ((void **) &stream->user_flags[j]);
+ stream->user_flags[j] = (char *) fs_get (k + 1);
+ strncpy (stream->user_flags[j],s,k);
+ stream->user_flags[j][k] = '\0';
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ /* possible X-UID */
+ else if (s[2] == 'U' && s[3] == 'I' && s[4] == 'D' &&
+ s[5] == ':') {
+ retain = NIL; /* don't retain continuation */
+ /* only believe if have a UID validity */
+ if (stream->uid_validity && ((nmsgs > 1) || !pseudoseen)) {
+ s += 6; /* advance to UID value */
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ j = 0;
+ /* found a digit? */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* flush remainder of line */
+ while (*s != '\n') s++;
+ /* make sure not duplicated */
+ if (elt->private.uid)
+ sprintf (tmp,"Message %lu UID %lu already has UID %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,elt->private.uid);
+ /* make sure UID doesn't go backwards */
+ else if (j <= prevuid)
+ sprintf (tmp,"Message %lu UID %lu less than %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,prevuid + 1);
+#if 0 /* this is currently broken by UIDPLUS */
+ /* or skip by mailbox's recorded last */
+ else if (j > stream->uid_last)
+ sprintf (tmp,"Message %lu UID %lu greater than last %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,stream->uid_last);
+#endif
+ else { /* normal UID case */
+ prevuid = elt->private.uid = j;
+#if 1 /* temporary kludge for UIDPLUS */
+ if (prevuid > stream->uid_last) {
+ stream->uid_last = prevuid;
+ LOCAL->ddirty = LOCAL->dirty = T;
+ }
+#endif
+ break; /* exit this cruft */
+ }
+ MM_LOG (tmp,WARN);
+ /* invalidate UID validity */
+ stream->uid_validity = 0;
+ elt->private.uid = 0;
+ }
+ break;
+ }
+ }
+ /* otherwise fall into S case */
+
+ case 'S': /* possible Status: line */
+ if (s[0] == 'S' && s[1] == 't' && s[2] == 'a' && s[3] == 't' &&
+ s[4] == 'u' && s[5] == 's' && s[6] == ':') {
+ retain = NIL; /* don't retain continuation */
+ s += 6; /* advance to status flags */
+ do switch (*s++) {/* parse flags */
+ case 'R': /* message read */
+ elt->seen = T;
+ break;
+ case 'O': /* message old */
+ if (elt->recent) {
+ elt->recent = NIL;
+ recent--; /* it really wasn't recent */
+ }
+ break;
+ case 'D': /* message deleted */
+ elt->deleted = T;
+ break;
+ case 'F': /* message flagged */
+ elt->flagged = T;
+ break;
+ case 'A': /* message answered */
+ elt->answered = T;
+ break;
+ case 'T': /* message is a draft */
+ elt->draft = T;
+ break;
+ default: /* some other crap */
+ break;
+ } while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n')));
+ break; /* all done */
+ }
+ /* otherwise fall into default case */
+
+ default: /* ordinary header line */
+ if ((*s == 'S') || (*s == 's') ||
+ (((*s == 'X') || (*s == 'x')) && (s[1] == '-'))) {
+ unsigned char *e,*v;
+ /* must match what mail_filter() does */
+ for (u = s,v = tmp,e = u + min (i,MAILTMPLEN - 1);
+ (u < e) && ((c = (*u ? *u : (*u = ' '))) != ':') &&
+ ((c > ' ') || ((c != ' ') && (c != '\t') &&
+ (c != '\r') && (c != '\n')));
+ *v++ = *u++);
+ *v = '\0'; /* tie off */
+ /* matches internal header? */
+ if (!compare_cstring (tmp,"STATUS") ||
+ !compare_cstring (tmp,"X-STATUS") ||
+ !compare_cstring (tmp,"X-KEYWORDS") ||
+ !compare_cstring (tmp,"X-UID") ||
+ !compare_cstring (tmp,"X-IMAP") ||
+ !compare_cstring (tmp,"X-IMAPBASE")) {
+ char err[MAILTMPLEN];
+ sprintf (err,"Discarding bogus %s header in message %lu",
+ (char *) tmp,elt->msgno);
+ MM_LOG (err,WARN);
+ retain = NIL; /* don't retain continuation */
+ break; /* different case or something */
+ }
+ }
+ /* retain or non-continuation? */
+ if (retain || ((*s != ' ') && (*s != '\t'))) {
+ retain = T; /* retaining continuation now */
+ /* line length in LF format newline */
+ for (j = k = 0; j < i; ++j) if (s[j] != '\r') ++k;
+ /* "internal" header size */
+ elt->private.spare.data += k;
+ /* message size */
+ elt->rfc822_size += k + 1;
+ }
+ else {
+ char err[MAILTMPLEN];
+ sprintf (err,"Discarding bogus continuation in msg %lu: %.80s",
+ elt->msgno,(char *) s);
+ if (u = strpbrk (err,"\r\n")) *u = '\0';
+ MM_LOG (err,WARN);
+ break; /* different case or something */
+ }
+ break;
+ }
+ } while (i && (*t != '\n') && ((*t != '\r') || (t[1] != '\n')));
+ /* "internal" header sans trailing newline */
+ if (i) elt->private.spare.data--;
+ /* assign a UID if none found */
+ if (((nmsgs > 1) || !pseudoseen) && !elt->private.uid) {
+ prevuid = elt->private.uid = ++stream->uid_last;
+ elt->private.dirty = T;
+ LOCAL->ddirty = T; /* force update */
+ }
+ else elt->private.dirty = elt->recent;
+
+ /* note size of header, location of text */
+ elt->private.msg.header.text.size =
+ (elt->private.msg.text.offset =
+ (LOCAL->filesize + GETPOS (&bs)) - elt->private.special.offset) -
+ elt->private.special.text.size;
+ k = m = 0; /* no previous line size yet */
+ /* note current position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ if (i) do { /* look for next message */
+ s = unix_mbxline (stream,&bs,&i);
+ if (i) { /* got new data? */
+ VALID (s,t,ti,zn); /* yes, parse line */
+ if (!ti) { /* not a header line, add it to message */
+ elt->rfc822_size += i;
+ for (j = 0; j < i; ++j) switch (s[j]) {
+ case '\r': /* squeeze out CRs */
+ elt->rfc822_size -= 1;
+ break;
+ case '\n': /* LF becomes CRLF */
+ elt->rfc822_size += 1;
+ break;
+ default:
+ break;
+ }
+ if ((i == 1) && (*s == '\n')) {
+ k = 2;
+ m = 1;
+ }
+ else if ((i == 2) && (*s == '\r') && (s[1] == '\n'))
+ k = m = 2;
+ else k = m = 0; /* file does not end with newline! */
+ /* update current position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ }
+ }
+ } while (i && !ti); /* until found a header */
+ elt->private.msg.text.text.size = j -
+ (elt->private.special.offset + elt->private.msg.text.offset);
+ /* flush ending blank line */
+ elt->private.msg.text.text.size -= m;
+ elt->rfc822_size -= k;
+ /* until end of buffer */
+ } while (!stream->sniff && i);
+ if (pseudoseen) { /* flush pseudo-message if present */
+ /* decrement recent count */
+ if (mail_elt (stream,1)->recent) recent--;
+ /* and the exists count */
+ mail_exists (stream,nmsgs--);
+ mail_expunged(stream,1);/* fake an expunge of that message */
+ }
+ /* need to start a new UID validity? */
+ if (!stream->uid_validity) {
+ stream->uid_validity = time (0);
+ /* in case a whiner with no life */
+ if (mail_parameters (NIL,GET_USERHASNOLIFE,NIL))
+ stream->uid_nosticky = T;
+ else if (nmsgs) { /* don't bother if empty file */
+ /* make dirty to restart UID epoch */
+ LOCAL->ddirty = LOCAL->dirty = T;
+ /* need to rewrite msg 1 if not pseudo */
+ if (!LOCAL->pseudo) mail_elt (stream,1)->private.dirty = T;
+ MM_LOG ("Assigning new unique identifiers to all messages",NIL);
+ }
+ }
+ stream->nmsgs = oldnmsgs; /* whack it back down */
+ stream->silent = silent; /* restore old silent setting */
+ /* notify upper level of new mailbox sizes */
+ mail_exists (stream,nmsgs);
+ mail_recent (stream,recent);
+ /* mark dirty so O flags are set */
+ if (recent) LOCAL->dirty = T;
+ }
+ }
+ /* no change, don't babble if never got time */
+ else if (LOCAL->filetime && LOCAL->filetime != sbuf.st_mtime)
+ MM_LOG ("New mailbox modification time but apparently no changes",WARN);
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ LOCAL->filetime = sbuf.st_mtime;
+ return T; /* return the winnage */
+}
+
+/* UNIX read line from mailbox
+ * Accepts: mail stream
+ * stringstruct
+ * pointer to line size
+ * Returns: pointer to input line
+ */
+
+char *unix_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size)
+{
+ unsigned long i,j,k,m;
+ char *s,*t,*te;
+ char *ret = "";
+ /* flush old buffer */
+ if (LOCAL->line) fs_give ((void **) &LOCAL->line);
+ /* if buffer needs refreshing */
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ if (SIZE (bs)) { /* find newline */
+ /* end of fast scan */
+ te = (t = (s = bs->curpos) + bs->cursize) - 12;
+ while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
+ --s; /* back up */
+ break; /* exit loop */
+ }
+ /* final character-at-a-time scan */
+ while ((s < t) && (*s != '\n')) ++s;
+ /* difficult case if line spans buffer */
+ if ((i = s - bs->curpos) == bs->cursize) {
+ /* have space in line buffer? */
+ if (i > LOCAL->linebuflen) {
+ fs_give ((void **) &LOCAL->linebuf);
+ LOCAL->linebuf = (char *) fs_get (LOCAL->linebuflen = i);
+ }
+ /* remember what we have so far */
+ memcpy (LOCAL->linebuf,bs->curpos,i);
+ /* load next buffer */
+ SETPOS (bs,k = GETPOS (bs) + i);
+ /* end of fast scan */
+ te = (t = (s = bs->curpos) + bs->cursize) - 12;
+ /* fast scan in overlap buffer */
+ while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
+ --s; /* back up */
+ break; /* exit loop */
+ }
+
+ /* final character-at-a-time scan */
+ while ((s < t) && (*s != '\n')) ++s;
+ /* huge line? */
+ if ((j = s - bs->curpos) == bs->cursize) {
+ SETPOS (bs,GETPOS (bs) + j);
+ /* look for end of line (s-l-o-w!!) */
+ for (m = SIZE (bs); m && (SNX (bs) != '\n'); --m,++j);
+ SETPOS (bs,k); /* go back to where it started */
+ }
+ /* got size of data, make buffer for return */
+ ret = LOCAL->line = (char *) fs_get (i + j + 2);
+ /* copy first chunk */
+ memcpy (ret,LOCAL->linebuf,i);
+ while (j) { /* copy remainder */
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ memcpy (ret + i,bs->curpos,k = min (j,bs->cursize));
+ i += k; /* account for this much read in */
+ j -= k;
+ bs->curpos += k; /* increment new position */
+ bs->cursize -= k; /* eat that many bytes */
+ }
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ /* read newline at end */
+ if (SIZE (bs)) ret[i++] = SNX (bs);
+ ret[i] = '\0'; /* makes debugging easier */
+ }
+ else { /* this is easy */
+ ret = bs->curpos; /* string it at this position */
+ bs->curpos += ++i; /* increment new position */
+ bs->cursize -= i; /* eat that many bytes */
+ }
+ *size = i; /* return that to user */
+ }
+ else *size = 0; /* end of data, return empty */
+ return ret;
+}
+
+/* UNIX make pseudo-header
+ * Accepts: MAIL stream
+ * buffer to write pseudo-header
+ * Returns: length of pseudo-header
+ */
+
+unsigned long unix_pseudo (MAILSTREAM *stream,char *hdr)
+{
+ int i;
+ char *s,tmp[MAILTMPLEN];
+ time_t now = time (0);
+ rfc822_fixed_date (tmp);
+ sprintf (hdr,"From %s %.24s\nDate: %s\nFrom: %s <%s@%.80s>\nSubject: %s\nMessage-ID: <%lu@%.80s>\nX-IMAP: %010lu %010lu",
+ pseudo_from,ctime (&now),
+ tmp,pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
+ (unsigned long) now,mylocalhost (),stream->uid_validity,
+ stream->uid_last);
+ for (s = hdr + strlen (hdr),i = 0; i < NUSERFLAGS; ++i)
+ if (stream->user_flags[i])
+ sprintf (s += strlen (s)," %s",stream->user_flags[i]);
+ sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n\n",pseudo_msg);
+ return strlen (hdr); /* return header length */
+}
+
+/* UNIX make status string
+ * Accepts: MAIL stream
+ * destination string to write
+ * message cache entry
+ * UID to write if non-zero (else use elt->private.uid)
+ * non-zero flag to write UID (.LT. 0 to write UID base info too)
+ * Returns: length of string
+ */
+
+unsigned long unix_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
+ unsigned long uid,long flag)
+{
+ char *t,stack[64];
+ char *s = status;
+ unsigned long n;
+ int pad = 50;
+ int sticky = uid ? T : !stream->uid_nosticky;
+ /* This used to use sprintf(), but thanks to certain cretinous C libraries
+ with horribly slow implementations of sprintf() I had to change it to this
+ mess. At least it should be fast. */
+ if ((flag < 0) && sticky) { /* need to write X-IMAPbase: header? */
+ *s++ = 'X'; *s++ = '-'; *s++ = 'I'; *s++ = 'M'; *s++ = 'A'; *s++ = 'P';
+ *s++ = 'b'; *s++ = 'a'; *s++ = 's'; *s++ = 'e'; *s++ = ':'; *s++ = ' ';
+ t = stack;
+ n = stream->uid_validity; /* push UID validity digits on the stack */
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ /* pop UID validity digits from stack */
+ while (t > stack) *s++ = *--t;
+ *s++ = ' ';
+ n = stream->uid_last; /* push UID last digits on the stack */
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ /* pop UID last digits from stack */
+ while (t > stack) *s++ = *--t;
+ for (n = 0; n < NUSERFLAGS; ++n) if (t = stream->user_flags[n])
+ for (*s++ = ' '; *t; *s++ = *t++);
+ *s++ = '\n';
+ pad += 30; /* increased padding if have IMAPbase */
+ }
+ *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't'; *s++ = 'u'; *s++ = 's';
+ *s++ = ':'; *s++ = ' ';
+ if (elt->seen) *s++ = 'R';
+ /* only write O if have a UID */
+ if (flag && (!elt->recent || !LOCAL->appending)) *s++ = 'O';
+ *s++ = '\n';
+ *s++ = 'X'; *s++ = '-'; *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't';
+ *s++ = 'u'; *s++ = 's'; *s++ = ':'; *s++ = ' ';
+ if (elt->deleted) *s++ = 'D';
+ if (elt->flagged) *s++ = 'F';
+ if (elt->answered) *s++ = 'A';
+ if (elt->draft) *s++ = 'T';
+ *s++ = '\n';
+
+ if (sticky) { /* only do this if UIDs sticky */
+ *s++ = 'X'; *s++ = '-'; *s++ = 'K'; *s++ = 'e'; *s++ = 'y'; *s++ = 'w';
+ *s++ = 'o'; *s++ = 'r'; *s++ = 'd'; *s++ = 's'; *s++ = ':';
+ if (n = elt->user_flags) do {
+ *s++ = ' ';
+ for (t = stream->user_flags[find_rightmost_bit (&n)]; *t; *s++ = *t++);
+ } while (n);
+ n = s - status; /* get size of stuff so far */
+ /* pad X-Keywords to make size constant */
+ if (n < pad) for (n = pad - n; n > 0; --n) *s++ = ' ';
+ *s++ = '\n';
+ if (flag) { /* want to include UID? */
+ t = stack;
+ /* push UID digits on the stack */
+ n = uid ? uid : elt->private.uid;
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ *s++ = 'X'; *s++ = '-'; *s++ = 'U'; *s++ = 'I'; *s++ = 'D'; *s++ = ':';
+ *s++ = ' ';
+ /* pop UID from stack */
+ while (t > stack) *s++ = *--t;
+ *s++ = '\n';
+ }
+ }
+ *s++ = '\n'; *s = '\0'; /* end of extended message status */
+ return s - status; /* return size of resulting string */
+}
+
+/* Rewrite mailbox file
+ * Accepts: MAIL stream, must be critical and locked
+ * return pointer to number of expunged messages if want expunge
+ * lock file name
+ * expunge sequence, not deleted flag
+ * Returns: T if success and mailbox unlocked, NIL if failure
+ */
+
+#define OVERFLOWBUFLEN 8192 /* initial overflow buffer length */
+
+long unix_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock,
+ long flags)
+{
+ MESSAGECACHE *elt;
+ UNIXFILE f;
+ char *s;
+ time_t tp[2];
+ long ret,flag;
+ unsigned long i,j;
+ unsigned long recent = stream->recent;
+ unsigned long size = LOCAL->pseudo ? unix_pseudo (stream,LOCAL->buf) : 0;
+ if (nexp) *nexp = 0; /* initially nothing expunged */
+ /* calculate size of mailbox after rewrite */
+ for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs; i++) {
+ elt = mail_elt (stream,i); /* get cache */
+ if (!(nexp && elt->deleted && (flags ? elt->sequence : T))) {
+ /* add RFC822 size of this message */
+ size += elt->private.special.text.size + elt->private.spare.data +
+ unix_xstatus (stream,LOCAL->buf,elt,NIL,flag) +
+ elt->private.msg.text.text.size + 1;
+ flag = 1; /* only count X-IMAPbase once */
+ }
+ }
+ /* no messages, has a life, and no pseudo */
+ if (!size && !mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) {
+ LOCAL->pseudo = T; /* so make a pseudo-message now */
+ size = unix_pseudo (stream,LOCAL->buf);
+ }
+ /* extend the file as necessary */
+ if (ret = unix_extend (stream,size)) {
+ /* Set up buffered I/O file structure
+ * curpos current position being written through buffering
+ * filepos current position being written physically to the disk
+ * bufpos current position being written in the buffer
+ * protect current maximum position that can be written to the disk
+ * before buffering is forced
+ * The code tries to buffer so that that disk is written in multiples of
+ * OVERBLOWBUFLEN bytes.
+ */
+ f.stream = stream; /* note mail stream */
+ f.curpos = f.filepos = 0; /* start of file */
+ f.protect = stream->nmsgs ? /* initial protection pointer */
+ mail_elt (stream,1)->private.special.offset : 8192;
+ f.bufpos = f.buf = (char *) fs_get (f.buflen = OVERFLOWBUFLEN);
+
+ if (LOCAL->pseudo) /* update pseudo-header */
+ unix_write (&f,LOCAL->buf,unix_pseudo (stream,LOCAL->buf));
+ /* loop through all messages */
+ for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs;) {
+ elt = mail_elt (stream,i);/* get cache */
+ /* expunge this message? */
+ if (nexp && elt->deleted && (flags ? elt->sequence : T)) {
+ /* one less recent message */
+ if (elt->recent) --recent;
+ mail_expunged(stream,i);/* notify upper levels */
+ ++*nexp; /* count up one more expunged message */
+ }
+ else { /* preserve this message */
+ i++; /* advance to next message */
+ if ((flag < 0) || /* need to rewrite message? */
+ elt->private.dirty || (f.curpos != elt->private.special.offset) ||
+ (elt->private.msg.header.text.size !=
+ (elt->private.spare.data +
+ unix_xstatus (stream,LOCAL->buf,elt,NIL,flag)))) {
+ unsigned long newoffset = f.curpos;
+ /* yes, seek to internal header */
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
+ /* see if need to squeeze out a CR */
+ if (LOCAL->buf[elt->private.special.text.size - 2] == '\r') {
+ LOCAL->buf[--elt->private.special.text.size - 1] = '\n';
+ --size; /* squeezed out a CR from PC */
+ }
+ /* protection pointer moves to RFC822 header */
+ f.protect = elt->private.special.offset +
+ elt->private.msg.header.offset;
+ /* write internal header */
+ unix_write (&f,LOCAL->buf,elt->private.special.text.size);
+ /* get RFC822 header */
+ s = unix_header (stream,elt->msgno,&j,FT_INTERNAL);
+ /* in case this got decremented */
+ elt->private.msg.header.offset = elt->private.special.text.size;
+ /* header size, sans trailing newline */
+ if ((j < 2) || (s[j - 2] == '\n')) j--;
+ /* this can happen if CRs were squeezed */
+ if (j < elt->private.spare.data) {
+ /* so fix up counts */
+ size -= elt->private.spare.data - j;
+ elt->private.spare.data = j;
+ }
+ else if (j != elt->private.spare.data)
+ fatal ("header size inconsistent");
+ /* protection pointer moves to RFC822 text */
+ f.protect = elt->private.special.offset +
+ elt->private.msg.text.offset;
+ unix_write (&f,s,j); /* write RFC822 header */
+ /* write status and UID */
+ unix_write (&f,LOCAL->buf,
+ j = unix_xstatus (stream,LOCAL->buf,elt,NIL,flag));
+ flag = 1; /* only write X-IMAPbase once */
+ /* new file header size */
+ elt->private.msg.header.text.size = elt->private.spare.data + j;
+
+ /* did text move? */
+ if (f.curpos != f.protect) {
+ /* get message text */
+ s = unix_text_work (stream,elt,&j,FT_INTERNAL);
+ /* this can happen if CRs were squeezed */
+ if (j < elt->private.msg.text.text.size) {
+ /* so fix up counts */
+ size -= elt->private.msg.text.text.size - j;
+ elt->private.msg.text.text.size = j;
+ }
+ /* can't happen it says here */
+ else if (j > elt->private.msg.text.text.size)
+ fatal ("text size inconsistent");
+ /* new text offset, status/UID may change it */
+ elt->private.msg.text.offset = f.curpos - newoffset;
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset : (f.curpos + j + 1);
+ unix_write (&f,s,j);/* write text */
+ /* write trailing newline */
+ unix_write (&f,"\n",1);
+ }
+ else { /* tie off header and status */
+ unix_write (&f,NIL,NIL);
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset : size;
+ /* locate end of message text */
+ j = f.filepos + elt->private.msg.text.text.size;
+ /* trailing newline already there? */
+ if (f.protect == (j + 1)) f.curpos = f.filepos = f.protect;
+ else { /* trailing newline missing, write it */
+ f.curpos = f.filepos = j;
+ unix_write (&f,"\n",1);
+ }
+ }
+ /* new internal header offset */
+ elt->private.special.offset = newoffset;
+ elt->private.dirty =NIL;/* message is now clean */
+ }
+ else { /* no need to rewrite this message */
+ /* tie off previous message if needed */
+ unix_write (&f,NIL,NIL);
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset : size;
+ /* locate end of message text */
+ j = f.filepos + elt->private.special.text.size +
+ elt->private.msg.header.text.size +
+ elt->private.msg.text.text.size;
+ /* trailing newline already there? */
+ if (f.protect == (j + 1)) f.curpos = f.filepos = f.protect;
+ else { /* trailing newline missing, write it */
+ f.curpos = f.filepos = j;
+ unix_write (&f,"\n",1);
+ }
+ }
+ }
+ }
+
+ unix_write (&f,NIL,NIL); /* tie off final message */
+ if (size != f.filepos) fatal ("file size inconsistent");
+ fs_give ((void **) &f.buf); /* free buffer */
+ /* make sure tied off */
+ ftruncate (LOCAL->fd,LOCAL->filesize = size);
+ fsync (LOCAL->fd); /* make sure the updates take */
+ if (size && (flag < 0)) fatal ("lost UID base information");
+ /* no longer dirty */
+ LOCAL->ddirty = LOCAL->dirty = NIL;
+ /* notify upper level of new mailbox sizes */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ /* set atime to now, mtime a second earlier */
+ tp[1] = (tp[0] = time (0)) - 1;
+ /* set the times, note change */
+ if (!utime (stream->mailbox,tp)) LOCAL->filetime = tp[1];
+ close (LOCAL->fd); /* close and reopen file */
+ if ((LOCAL->fd = open (stream->mailbox,O_RDWR,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL)))
+ < 0) {
+ sprintf (LOCAL->buf,"Mailbox open failed, aborted: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ unix_abort (stream);
+ }
+ dotlock_unlock (lock); /* flush the lock file */
+ }
+ return ret; /* return state from algorithm */
+}
+
+/* Extend UNIX mailbox file
+ * Accepts: MAIL stream
+ * new desired size
+ * Return: T if success, else NIL
+ */
+
+long unix_extend (MAILSTREAM *stream,unsigned long size)
+{
+ unsigned long i = (size > LOCAL->filesize) ? size - LOCAL->filesize : 0;
+ if (i) { /* does the mailbox need to grow? */
+ if (i > LOCAL->buflen) { /* make sure have enough space */
+ /* this user won the lottery all right */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1);
+ }
+ memset (LOCAL->buf,'\0',i); /* get a block of nulls */
+ while (T) { /* until write successful or punt */
+ lseek (LOCAL->fd,LOCAL->filesize,L_SET);
+ if ((write (LOCAL->fd,LOCAL->buf,i) >= 0) && !fsync (LOCAL->fd)) break;
+ else {
+ long e = errno; /* note error before doing ftruncate */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ if (MM_DISKERROR (stream,e,NIL)) {
+ fsync (LOCAL->fd); /* user chose to punt */
+ sprintf (LOCAL->buf,"Unable to extend mailbox: %s",strerror (e));
+ if (!stream->silent) MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ }
+ }
+ }
+ return LONGT;
+}
+
+/* Write data to buffered file
+ * Accepts: buffered file pointer
+ * file data or NIL to indicate "flush buffer"
+ * date size (ignored for "flush buffer")
+ * Does not return until success
+ */
+
+void unix_write (UNIXFILE *f,char *buf,unsigned long size)
+{
+ unsigned long i,j,k;
+ if (buf) { /* doing buffered write? */
+ i = f->bufpos - f->buf; /* yes, get size of current buffer data */
+ /* yes, have space in current buffer chunk? */
+ if (j = i ? ((f->buflen - i) % OVERFLOWBUFLEN) : f->buflen) {
+ /* yes, fill up buffer as much as we can */
+ memcpy (f->bufpos,buf,k = min (j,size));
+ f->bufpos += k; /* new buffer position */
+ f->curpos += k; /* new current position */
+ if (j -= k) return; /* all done if still have buffer free space */
+ buf += k; /* full, get new unwritten data pointer */
+ size -= k; /* new data size */
+ i += k; /* new buffer data size */
+ }
+ /* This chunk of the buffer is full. See if can make some space by
+ * writing to the disk, if there's enough unprotected space to do so.
+ * Try to fill out any unaligned chunk, along with any subsequent full
+ * chunks that will fit in unprotected space.
+ */
+ /* any unprotected space we can write to? */
+ if (j = min (i,f->protect - f->filepos)) {
+ /* yes, filepos not at chunk boundary? */
+ if ((k = f->filepos % OVERFLOWBUFLEN) && ((k = OVERFLOWBUFLEN - k) < j))
+ j -= k; /* yes, and can write out partial chunk */
+ else k = 0; /* no partial chunk to write */
+ /* if at least a chunk free, write that too */
+ if (j > OVERFLOWBUFLEN) k += j - (j % OVERFLOWBUFLEN);
+ if (k) { /* write data if there is anything we can */
+ unix_phys_write (f,f->buf,k);
+ /* slide buffer */
+ if (i -= k) memmove (f->buf,f->buf + k,i);
+ f->bufpos = f->buf + i; /* new end of buffer */
+ }
+ }
+
+ /* Have flushed the buffer as best as possible. All done if no more
+ * data to write. Otherwise, if the buffer is empty AND if the unwritten
+ * data is larger than a chunk AND the unprotected space is also larger
+ * than a chunk, then write as many chunks as we can directly from the
+ * data. Buffer the rest, expanding the buffer as needed.
+ */
+ if (size) { /* have more data that we need to buffer? */
+ /* can write any of it to disk instead? */
+ if ((f->bufpos == f->buf) &&
+ ((j = min (f->protect - f->filepos,size)) > OVERFLOWBUFLEN)) {
+ /* write as much as we can right now */
+ unix_phys_write (f,buf,j -= (j % OVERFLOWBUFLEN));
+ buf += j; /* new data pointer */
+ size -= j; /* new data size */
+ f->curpos += j; /* advance current pointer */
+ }
+ if (size) { /* still have data that we need to buffer? */
+ /* yes, need to expand the buffer? */
+ if ((i = ((f->bufpos + size) - f->buf)) > f->buflen) {
+ /* note current position in buffer */
+ j = f->bufpos - f->buf;
+ i += OVERFLOWBUFLEN; /* yes, grow another chunk */
+ fs_resize ((void **) &f->buf,f->buflen = i - (i % OVERFLOWBUFLEN));
+ /* in case buffer relocated */
+ f->bufpos = f->buf + j;
+ }
+ /* buffer remaining data */
+ memcpy (f->bufpos,buf,size);
+ f->bufpos += size; /* new end of buffer */
+ f->curpos += size; /* advance current pointer */
+ }
+ }
+ }
+ else { /* flush buffer to disk */
+ unix_phys_write (f,f->buf,i = f->bufpos - f->buf);
+ f->bufpos = f->buf; /* reset buffer */
+ /* update positions */
+ f->curpos = f->protect = f->filepos;
+ }
+}
+
+/* Physical disk write
+ * Accepts: buffered file pointer
+ * buffer address
+ * buffer size
+ * Does not return until success
+ */
+
+void unix_phys_write (UNIXFILE *f,char *buf,size_t size)
+{
+ MAILSTREAM *stream = f->stream;
+ /* write data at desired position */
+ while (size && ((lseek (LOCAL->fd,f->filepos,L_SET) < 0) ||
+ (write (LOCAL->fd,buf,size) < 0))) {
+ int e;
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Unable to write to mailbox: %s",strerror (e = errno));
+ MM_LOG (tmp,ERROR);
+ MM_DISKERROR (NIL,e,T); /* serious problem, must retry */
+ }
+ f->filepos += size; /* update file position */
+}
+
+/* MBOX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mboxdriver = {
+ "mbox", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_LOCKING|DR_NONEWMAILRONLY,
+ (DRIVER *) NIL, /* next driver */
+ mbox_valid, /* mailbox is valid for us */
+ unix_parameters, /* manipulate parameters */
+ unix_scan, /* scan mailboxes */
+ unix_list, /* find mailboxes */
+ unix_lsub, /* find subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ mbox_create, /* create mailbox */
+ mbox_delete, /* delete mailbox */
+ mbox_rename, /* rename mailbox */
+ mbox_status, /* status of mailbox */
+ mbox_open, /* open mailbox */
+ unix_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message structure */
+ unix_header, /* fetch message header */
+ unix_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ unix_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mbox_ping, /* ping mailbox to see if still alive */
+ mbox_check, /* check for new messages */
+ mbox_expunge, /* expunge deleted messages */
+ unix_copy, /* copy messages to another mailbox */
+ mbox_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mboxproto = {&mboxdriver};
+
+/* MBOX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mbox_valid (char *name)
+{
+ /* only INBOX, mbox must exist */
+ if (!compare_cstring (name,"INBOX") && (unix_valid ("mbox") || !errno) &&
+ (unix_valid (sysinbox()) || !errno || (errno == ENOENT)))
+ return &mboxdriver;
+ return NIL; /* can't win (yet, anyway) */
+}
+
+/* MBOX mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mbox_create (MAILSTREAM *stream,char *mailbox)
+{
+ char tmp[MAILTMPLEN];
+ if (!compare_cstring (mailbox,"INBOX")) return unix_create (NIL,"mbox");
+ sprintf (tmp,"Can't create non-INBOX name as mbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+}
+
+
+/* MBOX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mbox_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return mbox_rename (stream,mailbox,NIL);
+}
+
+
+/* MBOX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long mbox_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ char tmp[MAILTMPLEN];
+ long ret = unix_rename (stream,"~/mbox",newname);
+ /* recreate file if renamed INBOX */
+ if (ret) unix_create (NIL,"mbox");
+ else MM_LOG (tmp,ERROR); /* log error */
+ return ret; /* return success */
+}
+
+/* MBOX Mail status
+ * Accepts: mail stream
+ * mailbox name
+ * status flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mbox_status (MAILSTREAM *stream,char *mbx,long flags)
+{
+ MAILSTATUS status;
+ unsigned long i;
+ MAILSTREAM *tstream = NIL;
+ MAILSTREAM *systream = NIL;
+ /* make temporary stream (unless this mbx) */
+ if (!stream && !(stream = tstream =
+ mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
+ status.flags = flags; /* return status values */
+ status.messages = stream->nmsgs;
+ status.recent = stream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
+ if (!mail_elt (stream,i)->seen) status.unseen++;
+ status.uidnext = stream->uid_last + 1;
+ status.uidvalidity = stream->uid_validity;
+ if (!status.recent && /* calculate post-snarf results */
+ (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) {
+ status.messages += systream->nmsgs;
+ status.recent += systream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1; i <= systream->nmsgs; i++)
+ if (!mail_elt (systream,i)->seen) status.unseen++;
+ /* kludge but probably good enough */
+ status.uidnext += systream->nmsgs;
+ }
+ MM_STATUS(stream,mbx,&status);/* pass status to main program */
+ if (tstream) mail_close (tstream);
+ if (systream) mail_close (systream);
+ return T; /* success */
+}
+
+/* MBOX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mbox_open (MAILSTREAM *stream)
+{
+ unsigned long i = 1;
+ unsigned long recent = 0;
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &mboxproto;
+ /* change mailbox file name */
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr ("mbox");
+ /* open mailbox, snarf new mail */
+ if (!(unix_open (stream) && mbox_ping (stream))) return NIL;
+ stream->inbox = T; /* mark that this is an INBOX */
+ /* notify upper level of mailbox sizes */
+ mail_exists (stream,stream->nmsgs);
+ while (i <= stream->nmsgs) if (mail_elt (stream,i++)->recent) ++recent;
+ mail_recent (stream,recent); /* including recent messages */
+ return stream;
+}
+
+/* MBOX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+static int snarfed = 0; /* number of snarfs */
+
+long mbox_ping (MAILSTREAM *stream)
+{
+ int sfd;
+ unsigned long size;
+ struct stat sbuf;
+ char *s;
+ DOTLOCK lock,lockx;
+ /* time to try snarf and sysinbox non-empty? */
+ if (LOCAL && !stream->rdonly && !stream->lock &&
+ (time (0) >= (LOCAL->lastsnarf +
+ (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) &&
+ !stat (sysinbox (),&sbuf) && sbuf.st_size) {
+ MM_CRITICAL (stream); /* yes, go critical */
+ /* open and lock sysinbox */
+ if ((sfd = unix_lock (sysinbox (),O_RDWR,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lockx,LOCK_EX)) >= 0) {
+ /* locked sysinbox in good format? */
+ if (fstat (sfd,&sbuf) || !(size = sbuf.st_size) ||
+ !unix_isvalid_fd (sfd)) {
+ sprintf (LOCAL->buf,"Mail drop %s is not in standard Unix format",
+ sysinbox ());
+ MM_LOG (LOCAL->buf,ERROR);
+ }
+ /* sysinbox good, parse and excl-lock mbox */
+ else if (unix_parse (stream,&lock,LOCK_EX)) {
+ lseek (sfd,0,L_SET); /* read entire sysinbox into memory */
+ read (sfd,s = (char *) fs_get (size + 1),size);
+ s[size] = '\0'; /* tie it off */
+ /* append to end of mbox */
+ lseek (LOCAL->fd,LOCAL->filesize,L_SET);
+
+ /* copy to mbox */
+ if ((write (LOCAL->fd,s,size) < 0) || fsync (LOCAL->fd)) {
+ sprintf (LOCAL->buf,"New mail move failed: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,WARN);
+ /* revert mbox to previous size */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ }
+ /* sysinbox better not have changed */
+ else if (fstat (sfd,&sbuf) || (size != sbuf.st_size)) {
+ sprintf (LOCAL->buf,"Mail drop %s lock failure, old=%lu now=%lu",
+ sysinbox (),size,(unsigned long) sbuf.st_size);
+ MM_LOG (LOCAL->buf,ERROR);
+ /* revert mbox to previous size */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ /* Believe it or not, a Singaporean government system actually had
+ * symlinks from /var/mail/user to ~user/mbox. To compound this
+ * error, they used an SVR4 system; BSD and OSF locks would have
+ * prevented it but not SVR4 locks.
+ */
+ if (!fstat (sfd,&sbuf) && (size == sbuf.st_size))
+ syslog (LOG_ALERT,"File %s and %s are the same file!",
+ sysinbox (),stream->mailbox);
+ }
+ else { /* data copied OK */
+ ftruncate (sfd,0); /* truncate sysinbox to zero bytes */
+ if (!snarfed++) { /* have we snarfed before? */
+ /* syslog if server, else user log */
+ sprintf (LOCAL->buf,"Moved %lu bytes of new mail to %s from %s",
+ size,stream->mailbox,sysinbox ());
+ if (strcmp ((char *) mail_parameters (NIL,GET_SERVICENAME,NIL),
+ "unknown"))
+ syslog (LOG_INFO,"%s host= %s",LOCAL->buf,tcp_clienthost ());
+ else MM_LOG (LOCAL->buf,WARN);
+ }
+ }
+ /* done with sysinbox text */
+ fs_give ((void **) &s);
+ /* all done with mbox */
+ unix_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* unlock the stream */
+ MM_NOCRITICAL (stream); /* done with critical */
+ }
+ /* all done with sysinbox */
+ unix_unlock (sfd,NIL,&lockx);
+ }
+ MM_NOCRITICAL (stream); /* done with critical */
+ LOCAL->lastsnarf = time (0);/* note time of last snarf */
+ }
+ return unix_ping (stream); /* do the unix routine now */
+}
+
+/* MBOX mail check mailbox
+ * Accepts: MAIL stream
+ */
+
+void mbox_check (MAILSTREAM *stream)
+{
+ /* do local ping, then do unix routine */
+ if (mbox_ping (stream)) unix_check (stream);
+}
+
+
+/* MBOX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long mbox_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret = unix_expunge (stream,sequence,options);
+ mbox_ping (stream); /* do local ping */
+ return ret;
+}
+
+
+/* MBOX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mbox_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ char tmp[MAILTMPLEN];
+ if (mbox_valid (mailbox)) return unix_append (stream,"mbox",af,data);
+ sprintf (tmp,"Can't append to that name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+}
diff --git a/imap/src/osdep/amiga/unix.h b/imap/src/osdep/amiga/unix.h
new file mode 100644
index 00000000..76cde8b5
--- /dev/null
+++ b/imap/src/osdep/amiga/unix.h
@@ -0,0 +1,219 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX mail routines, Amiga version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 20 December 1989
+ * Last Edited: 30 August 2006
+ */
+
+
+/* DEDICATION
+ *
+ * This file is dedicated to my dog, Unix, also known as Yun-chan and
+ * Unix J. Terwilliker Jehosophat Aloysius Monstrosity Animal Beast. Unix
+ * passed away at the age of 11 1/2 on September 14, 1996, 12:18 PM PDT, after
+ * a two-month bout with cirrhosis of the liver.
+ *
+ * He was a dear friend, and I miss him terribly.
+ *
+ * Lift a leg, Yunie. Luv ya forever!!!!
+ */
+
+/* Validate line
+ * Accepts: pointer to candidate string to validate as a From header
+ * return pointer to end of date/time field
+ * return pointer to offset from t of time (hours of ``mmm dd hh:mm'')
+ * return pointer to offset from t of time zone (if non-zero)
+ * Returns: t,ti,zn set if valid From string, else ti is NIL
+ */
+
+#define VALID(s,x,ti,zn) { \
+ int remote = 0; \
+ ti = 0; \
+ if ((*s == 'F') && (s[1] == 'r') && (s[2] == 'o') && (s[3] == 'm') && \
+ (s[4] == ' ')) { \
+ for (x = s + 5; *x && *x != '\012'; x++); \
+ if (*x) { \
+ if (x[-1] == '\015') --x; \
+ if (x - s >= 41) { \
+ for (zn = -1; x[zn] != ' '; zn--); \
+ if ((x[zn-1] == 'm') && (x[zn-2] == 'o') && (x[zn-3] == 'r') && \
+ (x[zn-4] == 'f') && (x[zn-5] == ' ') && (x[zn-6] == 'e') && \
+ (x[zn-7] == 't') && (x[zn-8] == 'o') && (x[zn-9] == 'm') && \
+ (x[zn-10] == 'e') && (x[zn-11] == 'r') && (x[zn-12] == ' '))\
+ { \
+ while (x[zn-13] == ' ') zn--; \
+ x += zn - 12; \
+ remote = 1; \
+ } \
+ } \
+ if (x - s >= 27) { \
+ if (x[-5] == ' ') { \
+ if (x[-8] == ':') zn = 0,ti = -5; \
+ else if (x[-9] == ' ') ti = zn = -9; \
+ else if ((x[-11] == ' ') && ((x[-10]=='+') || (x[-10]=='-'))) \
+ ti = zn = -11; \
+ } \
+ else if (x[-4] == ' ') { \
+ if (x[-9] == ' ') zn = -4,ti = -9; \
+ else if ( (x[-13] == ' ') && (x[-16] == ' ') \
+ && (x[-20] ==' ') && \
+ ( ((x[-22] == ' ') && (x[-23] == ',')) || \
+ ((x[-23] == ' ') && (x[-24] == ',')) ) ) { \
+ char weekday[4]={0,}, month[4]={0,}, time[11]={0,}; \
+ char tzone[4]={0,}; \
+ char realtime[80]; \
+ int day,year,start=-26; \
+ if (x[-23] == ' ') x--; \
+ sscanf(&x[start],"%3c, %d %s %d %s %s", \
+ weekday,&day,month,&year,time,tzone); \
+ sprintf(realtime,"%s %s %2d %s %d %s", \
+ weekday,month,day,time, \
+ ( (year < 100) ? year+1900 : year),tzone); \
+ if (remote) \
+ strcat(realtime," remote from "); \
+ else \
+ strcat(realtime,"\n"); \
+ strncpy(&x[start],realtime,strlen(realtime)); \
+ zn = -2; \
+ ti = -7; \
+ } \
+ } \
+ else if (x[-6] == ' ') { \
+ if ((x[-11] == ' ') && ((x[-5] == '+') || (x[-5] == '-'))) \
+ zn = -6,ti = -11; \
+ } \
+ else if (x[-9] == ' ') { \
+ if ( ( (x[-12] == ' ') && (x[-16] == ' ') && \
+ ( ((x[-18] == ' ') && (x[-19] == ',') ) || \
+ ((x[-19] == ' ') && (x[-20] == ',')) ) \
+ || \
+ ((x[-14] == ' ') && (x[-18] == ' ') && \
+ ( ((x[-20] == ' ') && (x[-21] == ',') ) || \
+ ((x[-21] == ' ') && (x[-22] == ',')) ) ) ) ) { \
+ char weekday[4]={0,}, month[4]={0,},time[11]={0,}; \
+ int day,year,start=-24; \
+ char realtime[80]; \
+ if (x[-12] == ' ') x++; \
+ if (x[-19] == ' ') x++; \
+ sscanf(&x[start],"%3c, %d %3c %d %s",weekday, \
+ &day,month,&year,time); \
+ sprintf(realtime,"%s %s %2d %s %d",weekday,month,day,time,\
+ ( (year < 100) ? year+1900 : year)); \
+ if (remote) \
+ strcat(realtime," remote from "); \
+ else \
+ strcat(realtime,"\n"); \
+ strncpy(&x[start],realtime,strlen(realtime)); \
+ ti=-5; \
+ zn=0; \
+ } \
+ } \
+ if (ti && !((x[ti - 3] == ':') && \
+ (x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') && \
+ (x[ti - 3] == ' ') && (x[ti - 7] == ' ') && \
+ (x[ti - 11] == ' '))) ti = 0; \
+ } \
+ } \
+ } \
+}
+
+/* You are not expected to understand this macro, but read the next page if
+ * you are not faint of heart.
+ *
+ * Known formats to the VALID macro are:
+ * From user Wed Dec 2 05:53 1992
+ * BSD From user Wed Dec 2 05:53:22 1992
+ * SysV From user Wed Dec 2 05:53 PST 1992
+ * rn From user Wed Dec 2 05:53:22 PST 1992
+ * From user Wed Dec 2 05:53 -0700 1992
+ * emacs From user Wed Dec 2 05:53:22 -0700 1992
+ * From user Wed Dec 2 05:53 1992 PST
+ * From user Wed Dec 2 05:53:22 1992 PST
+ * From user Wed Dec 2 05:53 1992 -0700
+ * Solaris From user Wed Dec 2 05:53:22 1992 -0700
+ *
+ * Amiga From user Wed, 6 Dec 92 05:53:22 who did this !!!
+ * CHANGED in place to
+ * From user Wed Dec 2 05:53:22 1992
+ *
+ * Plus all of the above with `` remote from xxx'' after it. Thank you very
+ * much, smail and Solaris, for making my life considerably more complicated.
+ */
+
+/*
+ * What? You want to understand the VALID macro anyway? Alright, since you
+ * insist. Actually, it isn't really all that difficult, provided that you
+ * take it step by step.
+ *
+ * Line 1 Initializes the return ti value to failure (0);
+ * Lines 2-3 Validates that the 1st-5th characters are ``From ''.
+ * Lines 4-6 Validates that there is an end of line and points x at it.
+ * Lines 7-14 First checks to see if the line is at least 41 characters long.
+ * If so, it scans backwards to find the rightmost space. From
+ * that point, it scans backwards to see if the string matches
+ * `` remote from''. If so, it sets x to point to the space at
+ * the start of the string.
+ * Line 15 Makes sure that there are at least 27 characters in the line.
+ * Lines 16-21 Checks if the date/time ends with the year (there is a space
+ * five characters back). If there is a colon three characters
+ * further back, there is no timezone field, so zn is set to 0
+ * and ti is set in front of the year. Otherwise, there must
+ * either to be a space four characters back for a three-letter
+ * timezone, or a space six characters back followed by a + or -
+ * for a numeric timezone; in either case, zn and ti become the
+ * offset of the space immediately before it.
+ * Lines 22-24 Are the failure case for line 14. If there is a space four
+ * characters back, it is a three-letter timezone; there must be a
+ * space for the year nine characters back. zn is the zone
+ * offset; ti is the offset of the space.
+ * Lines 25-28 Are the failure case for line 20. If there is a space six
+ * characters back, it is a numeric timezone; there must be a
+ * space eleven characters back and a + or - five characters back.
+ * zn is the zone offset; ti is the offset of the space.
+ * Line 29-32 If ti is valid, make sure that the string before ti is of the
+ * form www mmm dd hh:mm or www mmm dd hh:mm:ss, otherwise
+ * invalidate ti. There must be a colon three characters back
+ * and a space six or nine characters back (depending upon
+ * whether or not the character six characters back is a colon).
+ * There must be a space three characters further back (in front
+ * of the day), one seven characters back (in front of the month),
+ * and one eleven characters back (in front of the day of week).
+ * ti is set to be the offset of the space before the time.
+ *
+ * Why a macro? It gets invoked a *lot* in a tight loop. On some of the
+ * newer pipelined machines it is faster being open-coded than it would be if
+ * subroutines are called.
+ *
+ * Why does it scan backwards from the end of the line, instead of doing the
+ * much easier forward scan? There is no deterministic way to parse the
+ * ``user'' field, because it may contain unquoted spaces! Yes, I tested it to
+ * see if unquoted spaces were possible. They are, and I've encountered enough
+ * evil mail to be totally unwilling to trust that ``it will never happen''.
+ */
+
+/* Build parameters */
+
+#define KODRETRY 15 /* kiss-of-death retry in seconds */
+#define LOCKTIMEOUT 5 /* lock timeout in minutes */
+#define CHUNK 16384 /* read-in chunk size */
diff --git a/imap/src/osdep/amiga/write.c b/imap/src/osdep/amiga/write.c
new file mode 100644
index 00000000..c7854815
--- /dev/null
+++ b/imap/src/osdep/amiga/write.c
@@ -0,0 +1,59 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Write data, treating partial writes as an error
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 May 1995
+ * Last Edited: 30 August 2006
+ */
+
+/* The whole purpose of this unfortunate routine is to deal with DOS and
+ * certain cretinous versions of UNIX which decided that the "bytes actually
+ * written" return value from write() gave them license to use that for things
+ * that are really errors, such as disk quota exceeded, maximum file size
+ * exceeded, disk full, etc.
+ *
+ * BSD won't screw us this way on the local filesystem, but who knows what
+ * some NFS-mounted filesystem will do.
+ */
+
+#undef write
+
+/* Write data to file
+ * Accepts: file descriptor
+ * I/O vector structure
+ * number of vectors in structure
+ * Returns: number of bytes written if successful, -1 if failure
+ */
+
+long maxposint = (long)((((unsigned long) 1) << ((sizeof(int) * 8) - 1)) - 1);
+
+long safe_write (int fd,char *buf,long nbytes)
+{
+ long i,j;
+ if (nbytes > 0) for (i = nbytes; i; i -= j,buf += j) {
+ while (((j = write (fd,buf,(int) min (maxposint,i))) < 0) &&
+ (errno == EINTR));
+ if (j < 0) return j;
+ }
+ return nbytes;
+}
diff --git a/imap/src/osdep/dos/bezrkdos.c b/imap/src/osdep/dos/bezrkdos.c
new file mode 100644
index 00000000..81617380
--- /dev/null
+++ b/imap/src/osdep/dos/bezrkdos.c
@@ -0,0 +1,901 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Berkeley mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 24 June 1992
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Dedication:
+ * This file is dedicated with affection to those Merry Marvels of Musical
+ * Madness . . .
+ * -> The Incomparable Leland Stanford Junior University Marching Band <-
+ * who entertain, awaken, and outrage Stanford fans in the fact of repeated
+ * losing seasons and shattered Rose Bowl dreams [Cardinal just don't have
+ * HUSKY FEVER!!!].
+ *
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <sys\stat.h>
+#include <dos.h>
+#include "rfc822.h"
+#include "dummy.h"
+#include "misc.h"
+#include "fdstring.h"
+
+/* Berkeley I/O stream local data */
+
+typedef struct bezerk_local {
+ int fd; /* file descriptor for I/O */
+ off_t filesize; /* file size parsed */
+ char *buf; /* temporary buffer */
+} BEZERKLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((BEZERKLOCAL *) stream->local)
+
+/* Function prototypes */
+
+DRIVER *bezerk_valid (char *name);
+long bezerk_isvalid (char *name,char *tmp);
+int bezerk_valid_line (char *s,char **rx,int *rzn);
+void *bezerk_parameters (long function,void *value);
+void bezerk_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void bezerk_list (MAILSTREAM *stream,char *ref,char *pat);
+void bezerk_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long bezerk_create (MAILSTREAM *stream,char *mailbox);
+long bezerk_delete (MAILSTREAM *stream,char *mailbox);
+long bezerk_rename (MAILSTREAM *stream,char *old,char *newname);
+MAILSTREAM *bezerk_open (MAILSTREAM *stream);
+void bezerk_close (MAILSTREAM *stream,long options);
+char *bezerk_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long bezerk_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,
+ long flags);
+long bezerk_ping (MAILSTREAM *stream);
+void bezerk_check (MAILSTREAM *stream);
+long bezerk_expunge (MAILSTREAM *stream,char *sequence,long options);
+long bezerk_copy (MAILSTREAM *stream,char *sequence,char *mailbox,
+ long options);
+long bezerk_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+int bezerk_append_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg);
+void bezerk_gc (MAILSTREAM *stream,long gcflags);
+char *bezerk_file (char *dst,char *name);
+long bezerk_badname (char *tmp,char *s);
+long bezerk_parse (MAILSTREAM *stream);
+unsigned long bezerk_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size);
+
+/* Berkeley mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER bezerkdriver = {
+ "bezerk", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_LOWMEM|DR_CRLF|DR_NOSTICKY,
+ (DRIVER *) NIL, /* next driver */
+ bezerk_valid, /* mailbox is valid for us */
+ bezerk_parameters, /* manipulate parameters */
+ bezerk_scan, /* scan mailboxes */
+ bezerk_list, /* list mailboxes */
+ bezerk_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ bezerk_create, /* create mailbox */
+ bezerk_delete, /* delete mailbox */
+ bezerk_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ bezerk_open, /* open mailbox */
+ bezerk_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ bezerk_header, /* fetch message header */
+ bezerk_text, /* fetch message text */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ bezerk_ping, /* ping mailbox to see if still alive */
+ bezerk_check, /* check for new messages */
+ bezerk_expunge, /* expunge deleted messages */
+ bezerk_copy, /* copy messages to another mailbox */
+ bezerk_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM bezerkproto = {&bezerkdriver};
+
+/* Berkeley mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *bezerk_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return bezerk_isvalid (name,tmp) ? &bezerkdriver : (DRIVER *) NIL;
+}
+
+
+/* Berkeley mail test for valid mailbox
+ * Accepts: mailbox name
+ * Returns: T if valid, NIL otherwise
+ */
+
+long bezerk_isvalid (char *name,char *tmp)
+{
+ int fd;
+ long ret = NIL;
+ struct stat sbuf;
+ errno = EINVAL; /* assume invalid argument */
+ /* if file, get its status */
+ if ((*name != '{') && mailboxfile (tmp,name) && !stat (tmp,&sbuf)) {
+ if (!sbuf.st_size)errno = 0;/* empty file */
+ else if ((fd = open (tmp,O_BINARY|O_RDONLY,NIL)) >= 0) {
+ memset (tmp,'\0',MAILTMPLEN);
+ errno = -1; /* in case bezerk_valid_line fails */
+ if (read (fd,tmp,MAILTMPLEN-1) >= 0)
+ ret = bezerk_valid_line (tmp,NIL,NIL);
+ close (fd); /* close the file */
+ }
+ }
+ /* in case INBOX but not bezerk format */
+ else if ((errno == ENOENT) && ((name[0] == 'I') || (name[0] == 'i')) &&
+ ((name[1] == 'N') || (name[1] == 'n')) &&
+ ((name[2] == 'B') || (name[2] == 'b')) &&
+ ((name[3] == 'O') || (name[3] == 'o')) &&
+ ((name[4] == 'X') || (name[4] == 'x')) && !name[5]) errno = -1;
+ return ret; /* return what we should */
+}
+
+/* Validate line
+ * Accepts: pointer to candidate string to validate as a From header
+ * return pointer to end of date/time field
+ * return pointer to offset from t of time (hours of ``mmm dd hh:mm'')
+ * return pointer to offset from t of time zone (if non-zero)
+ * Returns: t,ti,zn set if valid From string, else ti is NIL
+ */
+
+int bezerk_valid_line (char *s,char **rx,int *rzn)
+{
+ char *x;
+ int zn;
+ int ti = 0;
+ /* line must begin with "From " */
+ if ((*s != 'F') || (s[1] != 'r') || (s[2] != 'o') || (s[3] != 'm') ||
+ (s[4] != ' ')) return NIL;
+ /* find end of line */
+ for (x = s + 5; *x && *x != '\012'; x++);
+ if (!x) return NIL; /* end of line not found */
+ if (x[-1] == '\015') x--; /* ignore CR */
+ if ((x - s < 27)) return NIL; /* line too short */
+ if (x - s >= 41) { /* possible search for " remote from " */
+ for (zn = -1; x[zn] != ' '; zn--);
+ if ((x[zn-1] == 'm') && (x[zn-2] == 'o') && (x[zn-3] == 'r') &&
+ (x[zn-4] == 'f') && (x[zn-5] == ' ') && (x[zn-6] == 'e') &&
+ (x[zn-7] == 't') && (x[zn-8] == 'o') && (x[zn-9] == 'm') &&
+ (x[zn-10] == 'e') && (x[zn-11] == 'r') && (x[zn-12] == ' '))
+ x += zn - 12;
+ }
+ if (x[-5] == ' ') { /* ends with year? */
+ /* no timezone? */
+ if (x[-8] == ':') zn = 0,ti = -5;
+ /* three letter timezone? */
+ else if (x[-9] == ' ') ti = zn = -9;
+ /* numeric timezone? */
+ else if ((x[-11]==' ') && ((x[-10]=='+') || (x[-10]=='-'))) ti = zn = -11;
+ }
+ else if (x[-4] == ' ') { /* no year and three leter timezone? */
+ if (x[-9] == ' ') zn = -4,ti = -9;
+ }
+ else if (x[-6] == ' ') { /* no year and numeric timezone? */
+ if ((x[-11] == ' ') && ((x[-5] == '+') || (x[-5] == '-')))
+ zn = -6,ti = -11;
+ }
+ /* time must be www mmm dd hh:mm[:ss] */
+ if (ti && !((x[ti - 3] == ':') &&
+ (x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') &&
+ (x[ti - 3] == ' ') && (x[ti - 7] == ' ') &&
+ (x[ti - 11] == ' '))) return NIL;
+ if (rx) *rx = x; /* set return values */
+ if (rzn) *rzn = zn;
+ return ti;
+}
+
+/* Berkeley manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *bezerk_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+
+/* Berkeley mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void bezerk_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* Berkeley mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void bezerk_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (stream,ref,pat);
+}
+
+
+/* Berkeley mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void bezerk_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (stream,ref,pat);
+}
+
+/* Berkeley mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long bezerk_create (MAILSTREAM *stream,char *mailbox)
+{
+ return dummy_create (stream,mailbox);
+}
+
+
+/* Berkeley mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long bezerk_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return dummy_delete (stream,mailbox);
+}
+
+
+/* Berkeley mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long bezerk_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ return dummy_rename (stream,old,newname);
+}
+
+/* Berkeley mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *bezerk_open (MAILSTREAM *stream)
+{
+ long i;
+ int fd;
+ char *s;
+ char tmp[MAILTMPLEN];
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &bezerkproto;
+ if (stream->local) fatal ("bezerk recycle stream");
+ if (!mailboxfile (tmp,stream->mailbox))
+ return (MAILSTREAM *) bezerk_badname (tmp,stream->mailbox);
+ if (((fd = open (tmp,O_BINARY|O_RDONLY,NIL)) < 0)) {
+ sprintf (tmp,"Can't open mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ stream->rdonly = T; /* this driver is readonly */
+ stream->local = fs_get (sizeof (BEZERKLOCAL));
+ /* canonicalize the stream mailbox name */
+ fs_give ((void **) &stream->mailbox);
+ if (s = strchr ((s = strrchr (tmp,'\\')) ? s : tmp,'.')) *s = '\0';
+ stream->mailbox = cpystr (tmp);
+ LOCAL->fd = fd; /* note the file */
+ LOCAL->filesize = 0; /* initialize parsed file size */
+ LOCAL->buf = NIL; /* initially no local buffer */
+ stream->sequence++; /* bump sequence number */
+ stream->uid_validity = time (0);
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (!bezerk_ping (stream)) return NIL;
+ if (!stream->nmsgs) mm_log ("Mailbox is empty",(long) NIL);
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft = NIL;
+ stream->perm_user_flags = NIL;
+ return stream; /* return stream to caller */
+}
+
+/* Berkeley mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void bezerk_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T;
+ if (options & CL_EXPUNGE) bezerk_expunge (stream,NIL,NIL);
+ close (LOCAL->fd); /* close the local file */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* Berkeley mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *bezerk_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ char tmp[MAILTMPLEN];
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get to header position */
+ lseek (LOCAL->fd,bezerk_hdrpos (stream,msgno,length),L_SET);
+ /* is buffer big enough? */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((size_t) *length + 1);
+ LOCAL->buf[*length] = '\0'; /* tie off string */
+ /* slurp the data */
+ read (LOCAL->fd,LOCAL->buf,(size_t) *length);
+ return LOCAL->buf;
+}
+
+
+/* Berkeley mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: T, always
+ */
+
+long bezerk_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ MESSAGECACHE *elt;
+ FDDATA d;
+ unsigned long hdrsize,hdrpos;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);/* if message not seen */
+ /* mark message as seen */
+ if (elt->seen && !(flags & FT_PEEK)) {
+ elt->seen = T;
+ mm_flags (stream,msgno);
+ }
+ /* get location of text data */
+ hdrpos = bezerk_hdrpos (stream,msgno,&hdrsize);
+ d.fd = LOCAL->fd; /* set initial stringstruct */
+ d.pos = hdrpos + hdrsize;
+ /* flush old buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ d.chunk = LOCAL->buf = (char *) fs_get ((size_t) d.chunksize = CHUNKSIZE);
+ INIT (bs,fd_string,(void *) &d,elt->rfc822_size - hdrsize);
+ return T; /* success */
+}
+
+/* Berkeley mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long bezerk_ping (MAILSTREAM *stream)
+{
+ /* punt if stream no longer alive */
+ if (!(stream && LOCAL)) return NIL;
+ /* parse mailbox, punt if parse dies */
+ return (bezerk_parse (stream)) ? T : NIL;
+}
+
+
+/* Berkeley mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void bezerk_check (MAILSTREAM *stream)
+{
+ unsigned long i = 1;
+ if (bezerk_ping (stream)) { /* ping mailbox */
+ /* get new message status */
+ while (i <= stream->nmsgs) mail_elt (stream,i++);
+ mm_log ("Check completed",(long) NIL);
+ }
+}
+
+/* Berkeley mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long bezerk_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",WARN);
+ return LONGT;
+}
+
+/* Berkeley mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long bezerk_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ char tmp[MAILTMPLEN];
+ struct stat sbuf;
+ MESSAGECACHE *elt;
+ unsigned long i,j,k;
+ int fd;
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* make sure valid mailbox */
+ if (!bezerk_isvalid (mailbox,tmp) && errno) {
+ if (errno == ENOENT)
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",
+ (long) NIL);
+ else if (pc) return (*pc) (stream,sequence,mailbox,options);
+ else if (mailboxfile (tmp,mailbox)) {
+ sprintf (tmp,"Not a Bezerk-format mailbox: %s",mailbox);
+ mm_log (tmp,ERROR);
+ }
+ else bezerk_badname (tmp,mailbox);
+ return NIL;
+ }
+ /* open the destination */
+ if (!mailboxfile (tmp,mailbox) ||
+ (fd = open (tmp,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,
+ S_IREAD|S_IWRITE)) < 0) {
+ sprintf (tmp,"Unable to open copy mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+
+ mm_critical (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ /* for each requested message */
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,SEEK_SET);
+ /* number of bytes to copy */
+ j = elt->private.msg.full.offset + elt->rfc822_size;
+ do { /* read from source position */
+ k = min (j,(unsigned long) MAILTMPLEN);
+ read (LOCAL->fd,tmp,(unsigned int) k);
+ if (write (fd,tmp,(unsigned int) k) < 0) {
+ sprintf (tmp,"Unable to write message: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ chsize (fd,sbuf.st_size);
+ close (fd); /* punt */
+ mm_nocritical (stream);
+ return NIL;
+ }
+ } while (j -= k); /* until done */
+ }
+ close (fd); /* close the file */
+ mm_nocritical (stream); /* release critical */
+ /* delete all requested messages */
+ if (options & CP_MOVE) for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) elt->deleted = T;
+ if (mail_parameters (NIL,GET_COPYUID,NIL))
+ mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN);
+ return T;
+}
+
+/* Berkeley mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+#define BUFLEN MAILTMPLEN
+
+long bezerk_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd;
+ unsigned long i,j;
+ char *flags,*date,buf[BUFLEN],tmp[MAILTMPLEN],file[MAILTMPLEN];
+ FILE *sf,*df;
+ MESSAGECACHE elt;
+ STRING *message;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = &bezerkproto;
+ /* make sure valid mailbox */
+ if (!bezerk_isvalid (mailbox,tmp) && errno) {
+ if (errno == ENOENT) {
+ if (((mailbox[0] == 'I') || (mailbox[0] == 'i')) &&
+ ((mailbox[1] == 'N') || (mailbox[1] == 'n')) &&
+ ((mailbox[2] == 'B') || (mailbox[2] == 'b')) &&
+ ((mailbox[3] == 'O') || (mailbox[3] == 'o')) &&
+ ((mailbox[4] == 'X') || (mailbox[4] == 'x')) && !mailbox[5])
+ bezerk_create (NIL,"INBOX");
+ else {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ }
+ else if (mailboxfile (tmp,mailbox)) {
+ sprintf (tmp,"Not a Bezerk-format mailbox: %.80ss",mailbox);
+ mm_log (tmp,ERROR);
+ }
+ else bezerk_badname (tmp,mailbox);
+ return NIL;
+ }
+ tzset (); /* initialize timezone stuff */
+ /* get first message */
+ if (!(*af) (stream,data,&flags,&date,&message)) return NIL;
+ if (!(sf = tmpfile ())) { /* must have scratch file */
+ sprintf (tmp,"Unable to create scratch file: %.80s",strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+
+ do { /* parse date */
+ if (!date) rfc822_date (date = tmp);
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ mm_log (tmp,ERROR);
+ }
+ else { /* user wants to suppress time zones? */
+ if (mail_parameters (NIL,GET_NOTIMEZONES,NIL)) {
+ time_t when = mail_longdate (&elt);
+ date = ctime (&when); /* use traditional date */
+ }
+ /* use POSIX-style date */
+ else date = mail_cdate (tmp,&elt);
+ if (!SIZE (message)) mm_log ("Append of zero-length message",ERROR);
+ else if (!bezerk_append_msg (stream,sf,flags,date,message)) {
+ sprintf (tmp,"Error writing scratch file: %.80s",strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+ /* get next message */
+ else if ((*af) (stream,data,&flags,&date,&message)) continue;
+ }
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ } while (message); /* until no more messages */
+ if (fflush (sf) || fstat (fileno (sf),&sbuf)) {
+ sprintf (tmp,"Error finishing scratch file: %.80s",strerror (errno));
+ mm_log (tmp,ERROR);
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ }
+ i = sbuf.st_size; /* size of scratch file */
+
+ mm_critical (stream); /* go critical */
+ /* open the destination */
+ if (!mailboxfile (tmp,mailbox) ||
+ ((fd = open (tmp,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,
+ S_IREAD|S_IWRITE)) < 0) ||
+ !(df = fdopen (fd,"ab"))) {
+ mm_nocritical (stream); /* done with critical */
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ while (i) /* until written all bytes */
+ if ((j = fread (buf,1,min ((long) BUFLEN,i),sf)) &&
+ (fwrite (buf,1,j,df) == j)) i -= j;
+ fclose (sf); /* done with scratch file */
+ /* make sure append wins */
+ if (i || (fflush (df) == EOF)) {
+ chsize (fd,sbuf.st_size); /* revert file */
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ sprintf (buf,"Message append failed: %s",strerror (errno));
+ mm_log (buf,ERROR);
+ ret = NIL; /* return error */
+ }
+ fclose (df);
+ mm_nocritical (stream); /* release critical */
+ if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
+ mm_log ("Can not return meaningful APPENDUID with this mailbox format",
+ WARN);
+ return ret;
+}
+
+/* Write single message to append scratch file
+ * Accepts: MAIL stream
+ * scratch file
+ * flags
+ * message stringstruct
+ * Returns: NIL if write error, else T
+ */
+
+int bezerk_append_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg)
+{
+ int c;
+ unsigned long i,uf;
+ char tmp[MAILTMPLEN];
+ long f = mail_parse_flags (stream,flags,&uf);
+ /* build initial header */
+ if ((fprintf (sf,"From %s@%s %sStatus: ",
+ myusername (),mylocalhost (),date) < 0) ||
+ (f&fSEEN && (putc ('R',sf) == EOF)) ||
+ (fputs ("\nX-Status: ",sf) == EOF) ||
+ (f&fDELETED && (putc ('D',sf) == EOF)) ||
+ (f&fFLAGGED && (putc ('F',sf) == EOF)) ||
+ (f&fANSWERED && (putc ('A',sf) == EOF)) ||
+ (f&fDRAFT && (putc ('T',sf) == EOF)) ||
+ (fputs ("\nX-Keywords:",sf) == EOF)) return NIL;
+ while (uf) /* write user flags */
+ if (fprintf (sf," %s",stream->user_flags[find_rightmost_bit (&uf)]) < 0)
+ return NIL;
+ /* tie off flags */
+ if (putc ('\n',sf) == EOF) return NIL;
+ while (SIZE (msg)) { /* copy text to scratch file */
+ /* possible delimiter if line starts with F */
+ if ((c = 0xff & SNX (msg)) == 'F') {
+ /* copy line to buffer */
+ for (i = 1,tmp[0] = c; SIZE (msg) && (c != '\n') && (i < MAILTMPLEN);)
+ if (((c = 0xff & SNX (msg)) != '\r') || !(SIZE (msg)) ||
+ (CHR (msg) != '\n')) tmp[i++] = c;
+ if ((i > 4) && (tmp[1] == 'r') && (tmp[2] == 'o') && (tmp[3] == 'm') &&
+ (tmp[4] == ' ')) { /* possible "From " line? */
+ /* yes, see if need to write a widget */
+ if (((c != '\n') || bezerk_valid_line (tmp,NIL,NIL)) &&
+ (putc ('>',sf) == EOF)) return NIL;
+ }
+ /* write buffered text */
+ if (fwrite (tmp,1,i,sf) != i) return NIL;
+ if (c == '\n') continue; /* all done if got a complete line */
+ }
+ /* copy line, toss out CR from CRLF */
+ do if (((c == '\r') && SIZE (msg) && ((c = 0xff & SNX (msg)) != '\n') &&
+ (putc ('\r',sf) == EOF)) || (putc (c,sf) == EOF)) return NIL;
+ while ((c != '\n') && SIZE (msg) && ((c = 0xff & SNX (msg)) ? c : T));
+ }
+ /* write trailing newline and return */
+ return (putc ('\n',sf) == EOF) ? NIL : T;
+}
+
+
+/* Return bad file name error message
+ * Accepts: temporary buffer
+ * file name
+ * Returns: long NIL always
+ */
+
+long bezerk_badname (char *tmp,char *s)
+{
+ sprintf (tmp,"Invalid mailbox name: %s",s);
+ mm_log (tmp,ERROR);
+ return (long) NIL;
+}
+
+/* Parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long bezerk_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt;
+ char *s,*t,tmp[MAILTMPLEN + 1],*db,datemsg[100];
+ long i;
+ int j,ti,zn;
+ long curpos = LOCAL->filesize;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ short silent = stream->silent;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %ld to %ld!",curpos,sbuf.st_size);
+ mm_log (tmp,ERROR);
+ bezerk_close (stream,NIL);
+ return NIL;
+ }
+ stream->silent = T; /* don't pass up mm_exists() events yet */
+ db = datemsg + strlen (strcpy (datemsg,"Unparsable date: "));
+ /* while there is data to read */
+ while (i = sbuf.st_size - curpos){
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,SEEK_SET);
+ /* read first buffer's worth */
+ read (LOCAL->fd,tmp,j = (int) min (i,(long) MAILTMPLEN));
+ tmp[j] = '\0'; /* tie off buffer */
+ if (!(ti = bezerk_valid_line (tmp,&t,&zn))) {
+ mm_log ("Mailbox format invalidated (consult an expert), aborted",ERROR);
+ bezerk_close (stream,NIL);
+ return NIL;
+ }
+
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ /* instantiate an elt for this message */
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ elt->private.uid = ++stream->uid_last;
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* note offset of message */
+ elt->private.msg.full.offset =
+ (s = ((*t == '\015') ? (t + 2) : (t + 1))) - tmp;
+ /* generate plausable IMAPish date string */
+ db[2] = db[6] = db[20] = '-'; db[11] = ' '; db[14] = db[17] = ':';
+ /* dd */
+ db[0] = t[ti - 2]; db[1] = t[ti - 1];
+ /* mmm */
+ db[3] = t[ti - 6]; db[4] = t[ti - 5]; db[5] = t[ti - 4];
+ /* hh */
+ db[12] = t[ti + 1]; db[13] = t[ti + 2];
+ /* mm */
+ db[15] = t[ti + 4]; db[16] = t[ti + 5];
+ if (t[ti += 6] == ':') { /* ss if present */
+ db[18] = t[++ti]; db[19] = t[++ti];
+ ti++; /* move to space */
+ }
+ else db[18] = db[19] = '0'; /* assume 0 seconds */
+ /* yy -- advance over timezone if necessary */
+ if (++zn == ++ti) ti += (((t[zn] == '+') || (t[zn] == '-')) ? 6 : 4);
+ db[7] = t[ti]; db[8] = t[ti + 1]; db[9] = t[ti + 2]; db[10] = t[ti + 3];
+ t = zn ? (t + zn) : "LCL"; /* zzz */
+ db[21] = *t++; db[22] = *t++; db[23] = *t++;
+ if ((db[21] != '+') && (db[21] != '-')) db[24] = '\0';
+ else { /* numeric time zone */
+ db[20] = ' '; db[24] = *t++; db[25] = *t++; db[26] = '\0';
+ }
+ /* set internal date */
+ if (!mail_parse_date (elt,db)) mm_log (datemsg,WARN);
+
+ curpos += s - tmp; /* advance position after header */
+ t = strchr (s,'\012'); /* start of next line */
+ /* find start of next message */
+ while (!(bezerk_valid_line (s,NIL,NIL))) {
+ if (t) { /* have next line? */
+ t++; /* advance to new line */
+ curpos += t - s; /* update position and size */
+ elt->rfc822_size += ((t - s) + ((t[-2] == '\015') ? 0 : 1));
+ s = t; /* move to next line */
+ t = strchr (s,'\012');
+ }
+ else { /* try next buffer */
+ j = strlen (s); /* length of unread data in buffer */
+ if ((i = sbuf.st_size - curpos) && (i != j)) {
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,SEEK_SET);
+ /* read another buffer's worth */
+ read (LOCAL->fd,s = tmp,j = (int) min (i,(long) MAILTMPLEN));
+ tmp[j] = '\0'; /* tie off buffer */
+ if (!(t = strchr (s,'\012'))) fatal ("Line too long in mailbox");
+ }
+ else {
+ curpos += j; /* last bit of data */
+ elt->rfc822_size += j;
+ break;
+ }
+ }
+ }
+ }
+ /* update parsed file size */
+ LOCAL->filesize = sbuf.st_size;
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return T; /* return the winnage */
+}
+
+/* Berkeley locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * Returns: position of header in file
+ */
+
+unsigned long bezerk_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size)
+{
+ long siz;
+ size_t i = 0;
+ char c = '\0';
+ char *s;
+ char tmp[MAILTMPLEN];
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ long pos = elt->private.special.offset + elt->private.msg.full.offset;
+ /* is size known? */
+ if (!(*size = elt->private.msg.header.text.size)) {
+ /* get to header position */
+ lseek (LOCAL->fd,pos,SEEK_SET);
+ /* search message for CRLF CRLF */
+ for (siz = 1; siz <= elt->rfc822_size; siz++) {
+ if (!i && /* buffer empty? */
+ (read (LOCAL->fd,s = tmp,
+ i = (size_t) min(elt->rfc822_size-siz,(long)MAILTMPLEN))<= 0))
+ return pos;
+ else i--;
+ /* two newline sequence? */
+ if ((c == '\012') && (*s == '\012')) {
+ /* yes, note for later */
+ elt->private.msg.header.text.size = (*size = siz);
+ return pos; /* return to caller */
+ }
+ else if ((c == '\012') && (*s == '\015')) {
+ /* yes, note for later */
+ elt->private.msg.header.text.size = (*size = siz + 1);
+ return pos; /* return to caller */
+ }
+ else c = *s++; /* next character */
+ }
+ }
+ return pos; /* have position */
+}
diff --git a/imap/src/osdep/dos/drivers.bat b/imap/src/osdep/dos/drivers.bat
new file mode 100644
index 00000000..0964f537
--- /dev/null
+++ b/imap/src/osdep/dos/drivers.bat
@@ -0,0 +1,33 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Driver Linkage Generator for DOS/NT
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 11 October 1989
+REM Last Edited:30 August 2006
+
+REM Erase old driver linkage
+IF EXIST LINKAGE.* DEL LINKAGE.*
+
+REM Now define the new list
+FOR %%D IN (%1 %2 %3 %4 %5 %6 %7 %8 %9) DO CALL DRIVRAUX %%D
+
+EXIT 0
diff --git a/imap/src/osdep/dos/drivraux.bat b/imap/src/osdep/dos/drivraux.bat
new file mode 100644
index 00000000..a0b354cd
--- /dev/null
+++ b/imap/src/osdep/dos/drivraux.bat
@@ -0,0 +1,28 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Driver Linkage Generator auxillary for DOS
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 11 October 1989
+REM Last Edited:30 August 2006
+
+ECHO extern DRIVER %1driver; >> LINKAGE.H
+ECHO mail_link (&%1driver); /* link in the %1 driver */ >> LINKAGE.C
diff --git a/imap/src/osdep/dos/dummy.h b/imap/src/osdep/dos/dummy.h
new file mode 100644
index 00000000..32650e06
--- /dev/null
+++ b/imap/src/osdep/dos/dummy.h
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 9 May 1991
+ * Last Edited: 30 August 2006
+ */
+
+/* Exported function prototypes */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat);
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long scan_contents (DRIVER *dtb,char *name,char *contents,
+ unsigned long csiz,unsigned long fsiz);
+long dummy_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz);
+long dummy_create (MAILSTREAM *stream,char *mailbox);
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode);
+long dummy_delete (MAILSTREAM *stream,char *mailbox);
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname);
+char *dummy_file (char *dst,char *name);
+long dummy_canonicalize (char *tmp,char *ref,char *pat);
diff --git a/imap/src/osdep/dos/dummydos.c b/imap/src/osdep/dos/dummydos.c
new file mode 100644
index 00000000..4bf147b8
--- /dev/null
+++ b/imap/src/osdep/dos/dummydos.c
@@ -0,0 +1,689 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines for DOS
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 24 May 1993
+ * Last Edited: 30 August 2006
+ */
+
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include "mail.h"
+#include "osdep.h"
+#include <sys\stat.h>
+#include <dos.h>
+#include "dummy.h"
+#include "misc.h"
+
+/* Function prototypes */
+
+DRIVER *dummy_valid (char *name);
+void *dummy_parameters (long function,void *value);
+void dummy_list_work (MAILSTREAM *stream,char *dir,char *pat,char *contents,
+ long level);
+long dummy_listed (MAILSTREAM *stream,char delimiter,char *name,
+ long attributes,char *contents);
+long dummy_subscribe (MAILSTREAM *stream,char *mailbox);
+MAILSTREAM *dummy_open (MAILSTREAM *stream);
+void dummy_close (MAILSTREAM *stream,long options);
+long dummy_ping (MAILSTREAM *stream);
+void dummy_check (MAILSTREAM *stream);
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options);
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+long dummy_badname (char *tmp,char *s);
+
+/* Dummy routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER dummydriver = {
+ "dummy", /* driver name */
+ DR_LOCAL|DR_MAIL, /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ dummy_valid, /* mailbox is valid for us */
+ dummy_parameters, /* manipulate parameters */
+ dummy_scan, /* scan mailboxes */
+ dummy_list, /* list mailboxes */
+ dummy_lsub, /* list subscribed mailboxes */
+ dummy_subscribe, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ dummy_delete, /* delete mailbox */
+ dummy_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ dummy_open, /* open mailbox */
+ dummy_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message structure */
+ NIL, /* fetch header */
+ NIL, /* fetch text */
+ NIL, /* fetch message data */
+ NIL, /* unique identifier */
+ NIL, /* message number from UID */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ dummy_ping, /* ping mailbox to see if still alive */
+ dummy_check, /* check for new messages */
+ dummy_expunge, /* expunge deleted messages */
+ dummy_copy, /* copy messages to another mailbox */
+ dummy_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+
+ /* prototype stream */
+MAILSTREAM dummyproto = {&dummydriver};
+
+ /* driver parameters */
+static char *file_extension = NIL;
+
+/* Dummy validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *dummy_valid (char *name)
+{
+ char *s,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* must be valid local mailbox */
+ return (name && *name && (*name != '{') &&
+ (s = mailboxfile (tmp,name)) && (!*s || !stat (s,&sbuf))) ?
+ &dummydriver : NIL;
+}
+
+
+/* Dummy manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *dummy_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_EXTENSION:
+ if (file_extension) fs_give ((void **) &file_extension);
+ if (*(char *) value) file_extension = cpystr ((char *) value);
+ case GET_EXTENSION:
+ ret = (void *) file_extension;
+ }
+ return ret;
+}
+
+/* Dummy scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+#define LISTTMPLEN 128
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ char *s,test[LISTTMPLEN],file[LISTTMPLEN];
+ long i = 0;
+ if (!pat || !*pat) { /* empty pattern? */
+ if (dummy_canonicalize (test,ref,"*")) {
+ /* tie off name at root */
+ if (s = strchr (test,'\\')) *++s = '\0';
+ else test[0] = '\0';
+ dummy_listed (stream,'\\',test,LATT_NOINFERIORS,NIL);
+ }
+ }
+ /* get canonical form of name */
+ else if (dummy_canonicalize (test,ref,pat)) {
+ /* found any wildcards? */
+ if (s = strpbrk (test,"%*")) {
+ /* yes, copy name up to that point */
+ strncpy (file,test,(size_t) (i = s - test));
+ file[i] = '\0'; /* tie off */
+ }
+ else strcpy (file,test); /* use just that name then */
+ /* find directory name */
+ if (s = strrchr (file,'\\')) {
+ *++s = '\0'; /* found, tie off at that point */
+ s = file;
+ }
+ /* silly case */
+ else if (file[0] == '#') s = file;
+ /* do the work */
+ dummy_list_work (stream,s,test,contents,0);
+ if (pmatch ("INBOX",test)) /* always an INBOX */
+ dummy_listed (stream,NIL,"INBOX",LATT_NOINFERIORS,contents);
+ }
+}
+
+/* Dummy list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ dummy_scan (stream,ref,pat,NIL);
+}
+
+
+/* Dummy list subscribed mailboxes
+ * Accepts: mail stream
+ * pattern to search
+ */
+
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ void *sdb = NIL;
+ char *s,*t,test[MAILTMPLEN];
+ int showuppers = pat[strlen (pat) - 1] == '%';
+ /* get canonical form of name */
+ if (dummy_canonicalize (test,ref,pat) && (s = sm_read (&sdb))) do
+ if (*s != '{') {
+ if (pmatch_full (s,test,'\\')) {
+ if (pmatch (s,"INBOX")) mm_lsub (stream,NIL,s,LATT_NOINFERIORS);
+ else mm_lsub (stream,'\\',s,NIL);
+ }
+ else while (showuppers && (t = strrchr (s,'\\'))) {
+ *t = '\0'; /* tie off the name */
+ if (pmatch_full (s,test,'\\')) mm_lsub (stream,'\\',s,LATT_NOSELECT);
+ }
+ }
+ while (s = sm_read (&sdb)); /* until no more subscriptions */
+}
+
+
+/* Dummy subscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to add to subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_subscribe (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* must be valid local mailbox */
+ if ((s = mailboxfile (tmp,mailbox)) && *s && !stat (s,&sbuf) &&
+ ((sbuf.st_mode & S_IFMT) == S_IFREG)) return sm_subscribe (mailbox);
+ sprintf (tmp,"Can't subscribe %s: not a mailbox",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+}
+
+/* Dummy list mailboxes worker routine
+ * Accepts: mail stream
+ * directory name to search
+ * search pattern
+ * string to scan
+ * search level
+ */
+
+void dummy_list_work (MAILSTREAM *stream,char *dir,char *pat,char *contents,
+ long level)
+{
+ struct find_t f;
+ struct stat sbuf;
+ char *s,tmp[LISTTMPLEN],tmpx[LISTTMPLEN];
+ char *base = (dir && (dir[0] == '\\')) ? NIL : myhomedir ();
+ /* build name */
+ if (base) sprintf (tmpx,"%s\\",base);
+ else tmpx[0] = '\0';
+ if (dir) strcat (tmpx,dir);
+ /* punt if bogus name */
+ if (!mailboxfile (tmp,tmpx)) return;
+ /* make directory wildcard */
+ strcat (tmp,(tmp[strlen (tmp) -1] == '\\') ? "*." : "\\*.");
+ strcat (tmp,file_extension ? file_extension : "*");
+ /* do nothing if can't open directory */
+ if (!_dos_findfirst (tmp,_A_NORMAL|_A_SUBDIR,&f)) {
+ /* list it if at top-level */
+ if (!level && dir && pmatch_full (dir,pat,'\\'))
+ dummy_listed (stream,'\\',dir,LATT_NOSELECT,contents);
+ /* scan directory */
+ if (tmpx[strlen (tmpx) - 1] == '\\') do if (*f.name != '.') {
+ if (base) sprintf (tmpx,"%s\\",base);
+ else tmpx[0] = '\0';
+ if (dir) sprintf (tmpx + strlen (tmpx),"%s%s",dir,f.name);
+ else strcat (tmpx,f.name);
+ if (mailboxfile (tmp,tmpx) && !stat (tmp,&sbuf)) {
+ /* suppress extension */
+ if (file_extension && (s = strchr (f.name,'.'))) *s = '\0';
+ /* now make name we'd return */
+ if (dir) sprintf (tmp,"%s%s",dir,f.name);
+ else strcpy (tmp,f.name);
+ /* only interested in file type */
+ switch (sbuf.st_mode & S_IFMT) {
+ case S_IFDIR: /* directory? */
+ if (pmatch_full (tmp,pat,'\\')) {
+ dummy_listed (stream,'\\',tmp,LATT_NOSELECT,contents);
+ strcat (tmp,"\\"); /* set up for dmatch call */
+ }
+ /* try again with trailing / */
+ else if (pmatch_full (strcat (tmp,"\\"),pat,'\\'))
+ dummy_listed (stream,'\\',tmp,LATT_NOSELECT,contents);
+ if (dmatch (tmp,pat,'\\') &&
+ (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL)))
+ dummy_list_work (stream,tmp,pat,contents,level+1);
+ break;
+ case S_IFREG: /* ordinary name */
+ if (pmatch_full (tmp,pat,'\\') && !pmatch ("INBOX",tmp))
+ dummy_listed (stream,'\\',tmp,LATT_NOINFERIORS,contents);
+ break;
+ }
+ }
+ }
+ while (!_dos_findnext (&f));
+ }
+}
+
+/* Mailbox found
+ * Accepts: hierarchy delimiter
+ * mailbox name
+ * attributes
+ * contents to search before calling mm_list()
+ * Returns: T, always
+ */
+
+#define BUFSIZE MAILTMPLEN
+
+long dummy_listed (MAILSTREAM *stream,char delimiter,char *name,
+ long attributes,char *contents)
+{
+ struct stat sbuf;
+ int fd;
+ size_t csiz,ssiz,bsiz;
+ char *buf,tmp[MAILTMPLEN];
+ if (contents) { /* want to search contents? */
+ /* forget it if can't select or open */
+ if ((attributes & LATT_NOSELECT) || !(csiz = strlen (contents)) ||
+ !mailboxfile (tmp,name) || stat (tmp,&sbuf) || (csiz > sbuf.st_size) ||
+ ((fd = open (tmp,O_RDONLY,NIL)) < 0)) return T;
+ /* get buffer including slop */
+ buf = (char *) fs_get (BUFSIZE + (ssiz = 4 * ((csiz / 4) + 1)) + 1);
+ memset (buf,'\0',ssiz); /* no slop area the first time */
+ while (sbuf.st_size) { /* until end of file */
+ read (fd,buf+ssiz,bsiz = min (sbuf.st_size,BUFSIZE));
+ if (search ((unsigned char *) buf,bsiz+ssiz,
+ (unsigned char *) contents,csiz)) break;
+ memcpy (buf,buf+BUFSIZE,ssiz);
+ sbuf.st_size -= bsiz; /* note that we read that much */
+ }
+ fs_give ((void **) &buf); /* flush buffer */
+ close (fd); /* finished with file */
+ if (!sbuf.st_size) return T;/* not found */
+ }
+ /* notify main program */
+ mm_list (stream,delimiter,name,attributes);
+ return T;
+}
+
+/* Dummy create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create (MAILSTREAM *stream,char *mailbox)
+{
+ char tmp[MAILTMPLEN];
+ return (compare_cstring (mailbox,"INBOX") && mailboxfile (tmp,mailbox)) ?
+ dummy_create_path (stream,tmp,NIL) : dummy_badname (tmp,mailbox);
+}
+
+
+/* Dummy create path
+ * Accepts: mail stream
+ * path name to create
+ * directory mode
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode)
+{
+ struct stat sbuf;
+ char c,*s,tmp[MAILTMPLEN];
+ int fd;
+ long ret = NIL;
+ char *t = strrchr (path,'\\');
+ char *pt = (path[1] == ':') ? path + 2 : path;
+ int wantdir = t && !t[1];
+ if (wantdir) *t = '\0'; /* flush trailing delimiter for directory */
+ /* found superior to this name? */
+ if ((s = strrchr (pt,'\\')) && (s != pt)) {
+ strncpy (tmp,path,(size_t) (s - path));
+ tmp[s - path] = '\0'; /* make directory name for stat */
+ c = *++s; /* tie off in case need to recurse */
+ *s = '\0';
+ /* name doesn't exist, create it */
+ if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,path,dirmode)) return NIL;
+ *s = c; /* restore full name */
+ }
+ if (wantdir) { /* want to create directory? */
+ ret = !mkdir (path);
+ *t = '\\'; /* restore directory delimiter */
+ }
+ /* create file */
+ else if ((fd = open (path,O_WRONLY|O_CREAT|O_EXCL,S_IREAD|S_IWRITE)) >= 0)
+ ret = !close (fd); /* close file */
+ if (!ret) { /* error? */
+ sprintf (tmp,"Can't create mailbox node %s: %s",path,strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+ return ret; /* return status */
+}
+
+/* Dummy delete mailbox
+ * Accepts: mail stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_delete (MAILSTREAM *stream,char *mailbox)
+{
+ struct stat sbuf;
+ char *s,tmp[MAILTMPLEN];
+ if (!mailboxfile (tmp,mailbox)) return dummy_badname (tmp,mailbox);
+ /* no trailing \ */
+ if ((s = strrchr (tmp,'\\')) && !s[1]) *s = '\0';
+ if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) == S_IFDIR) ?
+ rmdir (tmp) : unlink (tmp)) {
+ sprintf (tmp,"Can't delete mailbox %s: %s",mailbox,strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ return T; /* return success */
+}
+
+
+/* Mail rename mailbox
+ * Accepts: mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ struct stat sbuf;
+ char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN];
+ /* make file name */
+ if (!mailboxfile (file,old)) return dummy_badname (tmp,old);
+ /* no trailing \ allowed */
+ if (!(s = mailboxfile (tmp,newname)) || ((s = strrchr (s,'\\')) && !s[1]))
+ return dummy_badname (tmp,newname);
+ if (s) { /* found superior to destination name? */
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (file,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create (stream,file)) return NIL;
+ *s = c; /* restore full name */
+ }
+ if (rename (file,tmp)) {
+ sprintf (tmp,"Can't rename mailbox %s to %s: %s",old,newname,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ return LONGT; /* return success */
+}
+
+/* Dummy open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *dummy_open (MAILSTREAM *stream)
+{
+ char tmp[MAILTMPLEN];
+ struct stat sbuf;
+ int fd = -1;
+ /* OP_PROTOTYPE call or silence */
+ if (!stream || stream->silent) return NIL;
+ if (!mailboxfile (tmp,stream->mailbox))
+ sprintf (tmp,"Can't open this name: %.80s",stream->mailbox);
+ else if (compare_cstring (stream->mailbox,"INBOX") &&
+ ((fd = open (tmp,O_RDONLY,NIL)) < 0))
+ sprintf (tmp,"%s: %s",strerror (errno),stream->mailbox);
+ else {
+ if (fd >= 0) { /* if got a file */
+ fstat (fd,&sbuf); /* sniff at its size */
+ close (fd);
+ if (sbuf.st_size) sprintf (tmp,"Not a mailbox: %s",stream->mailbox);
+ else fd = -1; /* a-OK */
+ }
+ if (fd < 0) { /* no file, right? */
+ if (!stream->silent) { /* only if silence not requested */
+ /* say there are 0 messages */
+ mail_exists (stream,(long) 0);
+ mail_recent (stream,(long) 0);
+ stream->uid_validity = time (0);
+ }
+ stream->inbox = T; /* note that it's an INBOX */
+ return stream; /* return success */
+ }
+ }
+ mm_log (tmp,stream->silent ? WARN: ERROR);
+ return NIL; /* always fails */
+}
+
+
+/* Dummy close
+ * Accepts: MAIL stream
+ * options
+ */
+
+void dummy_close (MAILSTREAM *stream,long options)
+{
+ /* return silently */
+}
+
+/* Dummy ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+long dummy_ping (MAILSTREAM *stream)
+{
+ MAILSTREAM *test;
+ /* time to do another test? */
+ if (time (0) >= ((time_t) (stream->gensym + 30))) {
+ /* has mailbox format changed? */
+ if ((test = mail_open (NIL,stream->mailbox,OP_PROTOTYPE)) &&
+ (test->dtb != stream->dtb) &&
+ (test = mail_open (NIL,stream->mailbox,NIL))) {
+ /* preserve some resources */
+ test->original_mailbox = stream->original_mailbox;
+ stream->original_mailbox = NIL;
+ test->sparep = stream->sparep;
+ stream->sparep = NIL;
+ test->sequence = stream->sequence;
+ mail_close ((MAILSTREAM *) /* flush resources used by dummy stream */
+ memcpy (fs_get (sizeof (MAILSTREAM)),stream,
+ sizeof (MAILSTREAM)));
+ /* swap the streams */
+ memcpy (stream,test,sizeof (MAILSTREAM));
+ fs_give ((void **) &test);/* flush test now that copied */
+ /* make sure application knows */
+ mail_exists (stream,stream->recent = stream->nmsgs);
+ }
+ /* still hasn't changed */
+ else stream->gensym = time (0);
+ }
+ return T;
+}
+
+
+/* Dummy check mailbox
+ * Accepts: MAIL stream
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+void dummy_check (MAILSTREAM *stream)
+{
+ dummy_ping (stream); /* invoke ping */
+}
+
+
+/* Dummy expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ return LONGT;
+}
+
+/* Dummy copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * options
+ * Returns: T if copy successful, else NIL
+ */
+
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) fatal ("Impossible dummy_copy");
+ return NIL;
+}
+
+
+/* Dummy append message string
+ * Accepts: mail stream
+ * destination mailbox
+ * append callback function
+ * data for callback
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd = -1;
+ int e;
+ char tmp[MAILTMPLEN];
+ MAILSTREAM *ts = default_proto (T);
+ if (compare_cstring (mailbox,"INBOX") && mailboxfile (tmp,mailbox) &&
+ ((fd = open (tmp,O_RDONLY,NIL)) < 0)) {
+ if ((e = errno) == ENOENT) /* failed, was it no such file? */
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",
+ (long) NIL);
+ sprintf (tmp,"%s: %s",strerror (e),mailbox);
+ mm_log (tmp,ERROR); /* pass up error */
+ return NIL; /* always fails */
+ }
+ if (fd >= 0) { /* found file? */
+ fstat (fd,&sbuf); /* get its size */
+ close (fd); /* toss out the fd */
+ if (sbuf.st_size) ts = NIL; /* non-empty file? */
+ }
+ if (ts) return (*ts->dtb->append) (stream,mailbox,af,data);
+ sprintf (tmp,"Indeterminate mailbox format: %s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+}
+
+/* Return bad file name error message
+ * Accepts: temporary buffer
+ * file name
+ * Returns: long NIL always
+ */
+
+long dummy_badname (char *tmp,char *s)
+{
+ sprintf (tmp,"Invalid mailbox name: %s",s);
+ mm_log (tmp,ERROR);
+ return (long) NIL;
+}
+
+
+/* Dummy canonicalize name
+ * Accepts: buffer to write name
+ * reference
+ * pattern
+ * Returns: T if success, NIL if failure
+ */
+
+long dummy_canonicalize (char *tmp,char *ref,char *pat)
+{
+ unsigned long i;
+ char *s,dev[4];
+ /* initially no device */
+ dev[0] = dev[1] = dev[2] = dev[3] = '\0';
+ if (ref) switch (*ref) { /* preliminary reference check */
+ case '{': /* remote names not allowed */
+ return NIL; /* disallowed */
+ case '\0': /* empty reference string */
+ break;
+ default: /* all other names */
+ if (ref[1] == ':') { /* start with device name? */
+ dev[0] = *ref++; dev[1] = *ref++;
+ }
+ break;
+ }
+ if (pat[1] == ':') { /* device name in pattern? */
+ dev[0] = *pat++; dev[1] = *pat++;
+ ref = NIL; /* ignore reference */
+ }
+ switch (*pat) {
+ case '#': /* namespace names */
+ if (mailboxfile (tmp,pat)) strcpy (tmp,pat);
+ else return NIL; /* unknown namespace */
+ break;
+ case '{': /* remote names not allowed */
+ return NIL;
+ case '\\': /* rooted name */
+ ref = NIL; /* ignore reference */
+ break;
+ }
+ /* make sure device names are rooted */
+ if (dev[0] && (*(ref ? ref : pat) != '\\')) dev[2] = '\\';
+ /* build name */
+ sprintf (tmp,"%s%s%s",dev,ref ? ref : "",pat);
+ ucase (tmp); /* force upper case */
+ /* count wildcards */
+ for (i = 0, s = tmp; *s; *s++) if ((*s == '*') || (*s == '%')) ++i;
+ if (i > MAXWILDCARDS) { /* ridiculous wildcarding? */
+ MM_LOG ("Excessive wildcards in LIST/LSUB",ERROR);
+ return NIL;
+ }
+ return T;
+}
diff --git a/imap/src/osdep/dos/env_dos.c b/imap/src/osdep/dos/env_dos.c
new file mode 100644
index 00000000..152dd322
--- /dev/null
+++ b/imap/src/osdep/dos/env_dos.c
@@ -0,0 +1,300 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: DOS environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+static char *myLocalHost = NIL; /* local host name */
+static char *myClientAddr = NIL;/* client host address */
+static char *myClientHost = NIL;/* client host name */
+static char *myServerAddr = NIL;/* server host address */
+static char *myServerHost = NIL;/* server host name */
+static char *myHomeDir = NIL; /* home directory name */
+static char *myNewsrc = NIL; /* newsrc file name */
+static long list_max_level = 5; /* maximum level of list recursion */
+static short no822tztext = NIL; /* disable RFC [2]822 timezone text */
+ /* home namespace */
+static NAMESPACE nshome = {"",'\\',NIL,NIL};
+ /* namespace list */
+static NAMESPACE *nslist[3] = {&nshome,NIL,NIL};
+
+#include "write.c" /* include safe writing routines */
+#include "pmatch.c" /* include wildcard pattern matcher */
+
+
+/* Dummy definitions to prevent errors */
+
+#define server_login(user,pass,authuser,argc,argv) NIL
+#define authserver_login(user,authuser,argc,argv) NIL
+#define myusername() ""
+#define MD5ENABLE "\\.nosuch.."
+
+
+/* Get all authenticators */
+
+#include "auths.c"
+
+/* Environment manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *env_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_NAMESPACE:
+ ret = (void *) nslist;
+ break;
+ case SET_HOMEDIR:
+ myHomeDir = cpystr ((char *) value);
+ case GET_HOMEDIR:
+ ret = (void *) myHomeDir;
+ break;
+ case SET_LOCALHOST:
+ myLocalHost = cpystr ((char *) value);
+ case GET_LOCALHOST:
+ ret = (void *) myLocalHost;
+ break;
+ case SET_NEWSRC:
+ if (myNewsrc) fs_give ((void **) &myNewsrc);
+ myNewsrc = cpystr ((char *) value);
+ case GET_NEWSRC:
+ if (!myNewsrc) { /* set news file name if not defined */
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"%s\\NEWSRC",myhomedir ());
+ myNewsrc = cpystr (tmp);
+ }
+ ret = (void *) myNewsrc;
+ break;
+ case SET_LISTMAXLEVEL:
+ list_max_level = (long) value;
+ case GET_LISTMAXLEVEL:
+ ret = (void *) list_max_level;
+ break;
+ case SET_DISABLE822TZTEXT:
+ no822tztext = value ? T : NIL;
+ case GET_DISABLE822TZTEXT:
+ ret = (void *) (no822tztext ? VOIDT : NIL);
+ break;
+ }
+ return ret;
+}
+
+/* Write current time
+ * Accepts: destination string
+ * optional format of day-of-week prefix
+ * format of date and time
+ * flag whether to append symbolic timezone
+ */
+
+static void do_date (char *date,char *prefix,char *fmt,int suffix)
+{
+ time_t tn = time (0);
+ struct tm *t = gmtime (&tn);
+ int zone = t->tm_hour * 60 + t->tm_min;
+ int julian = t->tm_yday;
+ t = localtime (&tn); /* get local time now */
+ /* minus UTC minutes since midnight */
+ zone = t->tm_hour * 60 + t->tm_min - zone;
+ /* julian can be one of:
+ * 36x local time is December 31, UTC is January 1, offset -24 hours
+ * 1 local time is 1 day ahead of UTC, offset +24 hours
+ * 0 local time is same day as UTC, no offset
+ * -1 local time is 1 day behind UTC, offset -24 hours
+ * -36x local time is January 1, UTC is December 31, offset +24 hours
+ */
+ if (julian = t->tm_yday -julian)
+ zone += ((julian < 0) == (abs (julian) == 1)) ? -24*60 : 24*60;
+ if (prefix) { /* want day of week? */
+ sprintf (date,prefix,days[t->tm_wday]);
+ date += strlen (date); /* make next sprintf append */
+ }
+ /* output the date */
+ sprintf (date,fmt,t->tm_mday,months[t->tm_mon],t->tm_year+1900,
+ t->tm_hour,t->tm_min,t->tm_sec,zone/60,abs (zone) % 60);
+ if (suffix) { /* append timezone suffix if desired */
+ tzset (); /* get timezone from TZ environment stuff */
+ sprintf (date + strlen (date)," (%.50s)",
+ tzname[daylight ? (((struct tm *) t)->tm_isdst > 0) : 0]);
+ }
+}
+
+
+/* Write current time in RFC 822 format
+ * Accepts: destination string
+ */
+
+void rfc822_date (char *date)
+{
+ do_date (date,"%s, ","%d %s %d %02d:%02d:%02d %+03d%02d",
+ no822tztext ? NIL : T);
+}
+
+
+/* Write current time in internal format
+ * Accepts: destination string
+ */
+
+void internal_date (char *date)
+{
+ do_date (date,NIL,"%02d-%s-%d %02d:%02d:%02d %+03d%02d",NIL);
+}
+
+/* Return my home directory name
+ * Returns: my home directory name
+ */
+
+char *myhomedir ()
+{
+ int i;
+ char *s;
+ if (!myHomeDir) { /* get home directory name if not yet known */
+ i = strlen (myHomeDir = cpystr ((s = getenv ("HOME")) ? s : ""));
+ if (i && ((myHomeDir[i-1] == '\\') || (myHomeDir[i-1]=='/')))
+ myHomeDir[i-1] = '\0'; /* tie off trailing directory delimiter */
+ }
+ return myHomeDir;
+}
+
+
+/* Return mailbox file name
+ * Accepts: destination buffer
+ * mailbox name
+ * Returns: file name
+ */
+
+char *mailboxfile (char *dst,char *name)
+{
+ char *s;
+ char *ext = (char *) mail_parameters (NIL,GET_EXTENSION,NIL);
+ /* forbid extraneous extensions */
+ if ((s = strchr ((s = strrchr (name,'\\')) ? s : name,'.')) &&
+ ((ext = (char *) mail_parameters (NIL,GET_EXTENSION,NIL)) ||
+ strchr (s+1,'.'))) return NIL;
+ /* absolute path name? */
+ if ((*name == '\\') || (name[1] == ':')) strcpy (dst,name);
+ else sprintf (dst,"%s\\%s",myhomedir (),name);
+ if (ext) sprintf (dst + strlen (dst),".%s",ext);
+ return ucase (dst);
+}
+
+
+/* Determine default prototype stream to user
+ * Accepts: type (NIL for create, T for append)
+ * Returns: default prototype stream
+ */
+
+MAILSTREAM *default_proto (long type)
+{
+ extern MAILSTREAM DEFAULTPROTO;
+ return &DEFAULTPROTO; /* return default driver's prototype */
+}
+
+/* Global data */
+
+static unsigned rndm = 0; /* initial `random' number */
+
+
+/* Return random number
+ */
+
+long random ()
+{
+ if (!rndm) srand (rndm = (unsigned) time (0L));
+ return (long) rand ();
+}
+
+/* Default mailgets routine on DOS
+ * Accepts: readin function pointer
+ * stream to use
+ * number of bytes
+ * identifier data
+ * Returns: string read in, truncated if necessary
+ *
+ * This is a sample mailgets routine. It simply truncates any data larger
+ * than 63K. On most systems, you generally don't use a mailgets
+ * routine at all, but on DOS it's required to prevent the application from
+ * crashing.
+ */
+
+static char *dos_gets_buf = NIL;
+
+char *dos_default_gets (readfn_t f,void *stream,unsigned long size,
+ GETS_DATA *md)
+{
+ readprogress_t *rp = mail_parameters (NIL,GET_READPROGRESS,NIL);
+ char *ret,tmp[MAILTMPLEN+1];
+ unsigned long i,j,dsc,rdi = 0;
+ unsigned long dos_max = 63 * 1024;
+ if (!dos_gets_buf) /* one-time initialization */
+ dos_gets_buf = (char *) fs_get ((size_t) dos_max + 1);
+ ret = (md->flags & MG_COPY) ?
+ ((char *) fs_get ((size_t) size + 1)) : dos_gets_buf;
+ if (size > dos_max) {
+ sprintf (tmp,"Mailbox %s, %s %lu[%.80s], %lu octets truncated to %ld",
+ md->stream->mailbox,(md->flags & MG_UID) ? "UID" : "#",
+ md->msgno,md->what,size,(long) dos_max);
+ mm_log (tmp,WARN); /* warn user */
+ dsc = size - dos_max; /* number of bytes to discard */
+ size = dos_max; /* maximum length string we can read */
+ }
+ else dsc = 0; /* nothing to discard */
+ dos_gets_buf[size] = '\0'; /* tie off string */
+ if (rp) for (i = size; j = min ((long) MAILTMPLEN,(long) i); i -= j) {
+ (*f) (stream,j,ret + rdi);
+ (*rp) (md,rdi += j);
+ }
+ else (*f) (stream,size,dos_gets_buf);
+ /* toss out everything after that */
+ for (i = dsc; j = min ((long) MAILTMPLEN,(long) i); i -= j) {
+ (*f) (stream,j,tmp);
+ if (rp) (*rp) (md,rdi += j);
+ }
+ return ret;
+}
+
+/* Emulator for BSD syslog() routine
+ * Accepts: priority
+ * message
+ * parameters
+ */
+
+void syslog (int priority,const char *message,...)
+{
+}
+
+
+/* Emulator for BSD openlog() routine
+ * Accepts: identity
+ * options
+ * facility
+ */
+
+void openlog (const char *ident,int logopt,int facility)
+{
+}
diff --git a/imap/src/osdep/dos/env_dos.h b/imap/src/osdep/dos/env_dos.h
new file mode 100644
index 00000000..bb0e8c0a
--- /dev/null
+++ b/imap/src/osdep/dos/env_dos.h
@@ -0,0 +1,68 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: DOS environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+#define SUBSCRIPTIONFILE(t) sprintf (t,"%s/MAILBOX.LST",myhomedir ())
+#define SUBSCRIPTIONTEMP(t) sprintf (t,"%s/MAILBOX.TMP",myhomedir ())
+
+#define L_SET SEEK_SET
+
+/* Function prototypes */
+
+#include "env.h"
+
+char *dos_default_gets (readfn_t f,void *stream,unsigned long size,
+ GETS_DATA *md);
+long safe_write (int fd,char *buf,long nbytes);
+long random ();
+#if _MSC_VER < 700
+#define getpid random
+#endif
+
+
+/* syslog() emulation */
+
+#define LOG_MAIL (2<<3) /* mail system */
+#define LOG_DAEMON (3<<3) /* system daemons */
+#define LOG_AUTH (4<<3) /* security/authorization messages */
+#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 signification condition */
+#define LOG_INFO 6 /* informational */
+#define LOG_DEBUG 7 /* debug-level messages */
+#define LOG_PID 0x01 /* log the pid with each message */
+#define LOG_CONS 0x02 /* log on the console if errors in sending */
+#define LOG_ODELAY 0x04 /* delay open until syslog() is called */
+#define LOG_NDELAY 0x08 /* don't delay open */
+#define LOG_NOWAIT 0x10 /* if forking to log on console, don't wait() */
+
+void openlog (const char *ident,int logopt,int facility);
+void syslog (int priority,const char *message,...);
diff --git a/imap/src/osdep/dos/fdstring.c b/imap/src/osdep/dos/fdstring.c
new file mode 100644
index 00000000..7a491f7d
--- /dev/null
+++ b/imap/src/osdep/dos/fdstring.c
@@ -0,0 +1,99 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: File descriptor string routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 April 1997
+ * Last Edited: 4 April 2007
+ */
+
+#include "mail.h"
+#include "osdep.h"
+#include "misc.h"
+#include "fdstring.h"
+
+/* String driver for fd stringstructs */
+
+static void fd_string_init (STRING *s,void *data,unsigned long size);
+static char fd_string_next (STRING *s);
+static void fd_string_setpos (STRING *s,unsigned long i);
+
+STRINGDRIVER fd_string = {
+ fd_string_init, /* initialize string structure */
+ fd_string_next, /* get next byte in string structure */
+ fd_string_setpos /* set position in string structure */
+};
+
+
+/* Initialize string structure for fd stringstruct
+ * Accepts: string structure
+ * pointer to string
+ * size of string
+ */
+
+static void fd_string_init (STRING *s,void *data,unsigned long size)
+{
+ FDDATA *d = (FDDATA *) data;
+ /* note fd */
+ s->data = (void *) (unsigned long) d->fd;
+ s->data1 = d->pos; /* note file offset */
+ s->size = size; /* note size */
+ s->curpos = s->chunk = d->chunk;
+ s->chunksize = (unsigned long) d->chunksize;
+ s->offset = 0; /* initial position */
+ /* and size of data */
+ s->cursize = min (s->chunksize,size);
+ /* move to that position in the file */
+ lseek (d->fd,d->pos,L_SET);
+ read (d->fd,s->chunk,(size_t) s->cursize);
+}
+
+/* Get next character from fd stringstruct
+ * Accepts: string structure
+ * Returns: character, string structure chunk refreshed
+ */
+
+static char fd_string_next (STRING *s)
+{
+ char c = *s->curpos++; /* get next byte */
+ SETPOS (s,GETPOS (s)); /* move to next chunk */
+ return c; /* return the byte */
+}
+
+
+/* Set string pointer position for fd stringstruct
+ * Accepts: string structure
+ * new position
+ */
+
+static void fd_string_setpos (STRING *s,unsigned long i)
+{
+ if (i > s->size) i = s->size; /* don't permit setting beyond EOF */
+ s->offset = i; /* set new offset */
+ s->curpos = s->chunk; /* reset position */
+ /* set size of data */
+ if (s->cursize = min (s->chunksize,SIZE (s))) {
+ /* move to that position in the file */
+ lseek ((long) s->data,s->data1 + s->offset,L_SET);
+ read ((long) s->data,s->curpos,(size_t) s->cursize);
+ }
+}
diff --git a/imap/src/osdep/dos/fdstring.h b/imap/src/osdep/dos/fdstring.h
new file mode 100644
index 00000000..d0a021bf
--- /dev/null
+++ b/imap/src/osdep/dos/fdstring.h
@@ -0,0 +1,39 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: File descriptor string routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 April 1997
+ * Last Edited: 30 August 2006
+ */
+
+/* Driver-dependent data passed to init method */
+
+typedef struct fd_data {
+ int fd; /* file descriptor */
+ unsigned long pos; /* initial position */
+ char *chunk; /* I/O buffer chunk */
+ unsigned long chunksize; /* I/O buffer chunk length */
+} FDDATA;
+
+
+extern STRINGDRIVER fd_string;
diff --git a/imap/src/osdep/dos/fs_dos.c b/imap/src/osdep/dos/fs_dos.c
new file mode 100644
index 00000000..03908328
--- /dev/null
+++ b/imap/src/osdep/dos/fs_dos.c
@@ -0,0 +1,62 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Free storage management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Get a block of free storage
+ * Accepts: size of desired block
+ * Returns: free storage block
+ */
+
+void *fs_get (size_t size)
+{
+ void *block = malloc (size ? size : (size_t) 1);
+ if (!block) fatal ("Out of memory");
+ return (block);
+}
+
+
+/* Resize a block of free storage
+ * Accepts: ** pointer to current block
+ * new size
+ */
+
+void fs_resize (void **block,size_t size)
+{
+ if (!(*block = realloc (*block,size ? size : (size_t) 1)))
+ fatal ("Can't resize memory");
+}
+
+
+/* Return a block of free storage
+ * Accepts: ** pointer to free storage block
+ */
+
+void fs_give (void **block)
+{
+ free (*block);
+ *block = NIL;
+}
diff --git a/imap/src/osdep/dos/ftl_dos.c b/imap/src/osdep/dos/ftl_dos.c
new file mode 100644
index 00000000..9e65ef55
--- /dev/null
+++ b/imap/src/osdep/dos/ftl_dos.c
@@ -0,0 +1,38 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: DOS/VMS/TOPS-20 crash management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Report a fatal error
+ * Accepts: string to output
+ */
+
+void fatal (char *string)
+{
+ mm_fatal (string); /* pass up the string */
+ abort (); /* die horribly */
+}
diff --git a/imap/src/osdep/dos/makefile b/imap/src/osdep/dos/makefile
new file mode 100644
index 00000000..40cf2276
--- /dev/null
+++ b/imap/src/osdep/dos/makefile
@@ -0,0 +1,98 @@
+# ========================================================================
+# Copyright 1988-2007 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# ========================================================================
+
+
+# Program: Portable C client makefile -- MS-DOS version
+#
+# Author: Mark Crispin
+# Networks and Distributed Computing
+# Computing & Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, WA 98195
+# Internet: MRC@CAC.Washington.EDU
+#
+# Date: 11 May 1989
+# Last Edited: 23 May 2007
+
+
+OS = wsk
+EXTRAAUTHENTICATORS=
+DEFAULTAUTHENTICATORS= ext md5 pla log
+EXTRADRIVERS =
+DRIVERS = imap nntp pop3 mtx bezerk
+DEFAULTDRIVER = mtx
+CFLAGS= -AL /DCHUNKSIZE=4096 -nologo $(EXTRACFLAGS)
+CC = cl
+
+all: mtest.exe
+
+.c.obj:
+ $(CC) -c $(CFLAGS) $*.c
+
+osdep.h: os_$(OS).h
+ copy os_$(OS).h osdep.h
+ drivers $(EXTRADRIVERS) $(DRIVERS) dummy
+ mkauths $(EXTRAAUTHENTICATORS) $(DEFAULTAUTHENTICATORS)
+ ECHO #define DEFAULTPROTO $(DEFAULTDRIVER)proto >> LINKAGE.H
+ ECHO if (!mail_parameters (NIL,GET_GETS)) >> LINKAGE.C
+ ECHO mail_parameters (NIL,SET_GETS,(void *) dos_default_gets); >> LINKAGE.C
+ ECHO mail_versioncheck (CCLIENTVERSION); >> LINKAGE.C
+
+mtest.obj: mail.h smtp.h misc.h osdep.h mtest.c
+
+mail.obj: mail.h misc.h osdep.h mail.c
+
+misc.obj: mail.h misc.h misc.c
+
+fdstring.obj: mail.h misc.h osdep.h fdstring.h fdstring.c
+
+flstring.obj: mail.h misc.h osdep.h flstring.h flstring.c
+
+netmsg.obj: mail.h misc.h netmsg.h osdep.h netmsg.c
+
+newsrc.obj: mail.h misc.h newsrc.h osdep.h newsrc.c
+
+rfc822.obj: mail.h rfc822.h misc.h rfc822.c
+
+smanager.obj: mail.h misc.h smanager.c
+
+utf8.obj: mail.h misc.h osdep.h utf8.h
+
+utf8aux.obj: mail.h misc.h osdep.h utf8.h
+
+imap4r1.obj: mail.h imap4r1.h misc.h osdep.h imap4r1.c
+
+nntp.obj: mail.h nntp.h smtp.h rfc822.h misc.h osdep.h nntp.c
+
+pop3.obj: mail.h rfc822.h misc.h osdep.h pop3.c
+
+smtp.obj: mail.h smtp.h rfc822.h misc.h osdep.h smtp.c
+
+os_$(OS).obj: mail.h osdep.h env_dos.h fs.h ftl.h nl.h tcp.h \
+ os_$(OS).c fs_dos.c ftl_dos.c nl_dos.c env_dos.c pmatch.c write.c
+
+mtxdos.obj: mail.h misc.h osdep.h mtxdos.c
+
+bezrkdos.obj: mail.h misc.h osdep.h bezrkdos.c
+
+dummydos.obj: mail.h dummy.h misc.h osdep.h dummydos.c
+
+cclient.lib: mail.obj misc.obj fdstring.obj flstring.obj netmsg.obj \
+ newsrc.obj rfc822.obj smanager.obj utf8.obj utf8aux.obj \
+ imap4r1.obj nntp.obj pop3.obj smtp.obj os_$(OS).obj \
+ mtxdos.obj bezrkdos.obj dummydos.obj
+ erase cclient.lib
+ lib cclient +mail+misc+fdstring+flstring+netmsg+newsrc+rfc822+smanager+utf8+utf8aux+imap4r1+nntp+pop3+smtp+os_$(OS)+mtxdos+bezrkdos+dummydos;
+
+mtest.exe: cclient.lib mtest.obj
+ mtest$(OS)
diff --git a/imap/src/osdep/dos/mkautaux.bat b/imap/src/osdep/dos/mkautaux.bat
new file mode 100644
index 00000000..5d24d83b
--- /dev/null
+++ b/imap/src/osdep/dos/mkautaux.bat
@@ -0,0 +1,29 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Authenticator Linkage Generator auxillary for DOS
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 7 December 1995
+REM Last Edited:30 August 2006
+
+ECHO extern AUTHENTICATOR auth_%1; >> LINKAGE.H
+ECHO auth_link (&auth_%1); /* link in the %1 authenticator */ >> LINKAGE.C
+ECHO #include "auth_%1.c" >> AUTHS.C
diff --git a/imap/src/osdep/dos/mkauths.bat b/imap/src/osdep/dos/mkauths.bat
new file mode 100644
index 00000000..d8c5e360
--- /dev/null
+++ b/imap/src/osdep/dos/mkauths.bat
@@ -0,0 +1,33 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Authenticator Linkage Generator for DOS and Windows
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 6 December 1995
+REM Last Edited:30 August 2006
+
+REM Erase old authenticators list
+IF EXIST AUTHS.C DEL AUTHS.C
+
+REM Now define the new list
+FOR %%D IN (%1 %2 %3 %4 %5 %6 %7 %8 %9) DO CALL MKAUTAUX %%D
+
+EXIT 0
diff --git a/imap/src/osdep/dos/mtestdbw.bat b/imap/src/osdep/dos/mtestdbw.bat
new file mode 100644
index 00000000..95452a01
--- /dev/null
+++ b/imap/src/osdep/dos/mtestdbw.bat
@@ -0,0 +1,27 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Portable C client makefile -- MS-DOS B&W link
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 26 June 1994
+REM Last Edited:30 August 2006
+
+link /NOI /stack:32767 mtest.obj,mtest.exe,,cclient.lib llbwtcp.lib llibce.lib
diff --git a/imap/src/osdep/dos/mtestdnf.bat b/imap/src/osdep/dos/mtestdnf.bat
new file mode 100644
index 00000000..451bdda3
--- /dev/null
+++ b/imap/src/osdep/dos/mtestdnf.bat
@@ -0,0 +1,27 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Portable C client makefile -- MS-DOS PC-NFS link
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 26 June 1994
+REM Last Edited:30 August 2006
+
+link /NOI /stack:32767 mtest.obj,mtest.exe,,cclient.lib ltklib.lib
diff --git a/imap/src/osdep/dos/mtestdnv.bat b/imap/src/osdep/dos/mtestdnv.bat
new file mode 100644
index 00000000..2b444e18
--- /dev/null
+++ b/imap/src/osdep/dos/mtestdnv.bat
@@ -0,0 +1,27 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Portable C client makefile -- MS-DOS Novell link
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 26 June 1994
+REM Last Edited:30 August 2006
+
+link /NOI /stack:32767 mtest.obj,mtest.exe,,cclient.lib llibsock.lib
diff --git a/imap/src/osdep/dos/mtestdpc.bat b/imap/src/osdep/dos/mtestdpc.bat
new file mode 100644
index 00000000..ca131181
--- /dev/null
+++ b/imap/src/osdep/dos/mtestdpc.bat
@@ -0,0 +1,27 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Portable C client makefile -- MS-DOS PC/TCP link
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 26 June 1994
+REM Last Edited:30 August 2006
+
+link /NOI /stack:32767 mtest.obj,mtest.exe,,cclient.lib lsocket.lib lnetlib.lib lpc.lib lconfig.lib llibce.lib;
diff --git a/imap/src/osdep/dos/mtestdwa.bat b/imap/src/osdep/dos/mtestdwa.bat
new file mode 100644
index 00000000..3d26146f
--- /dev/null
+++ b/imap/src/osdep/dos/mtestdwa.bat
@@ -0,0 +1,27 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Portable C client makefile -- MS-DOS Waterloo link
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 26 June 1994
+REM Last Edited:30 August 2006
+
+link /NOI /stack:32767 mtest.obj,mtest.exe,,cclient.lib wattcplg.lib
diff --git a/imap/src/osdep/dos/mtestwsk.bat b/imap/src/osdep/dos/mtestwsk.bat
new file mode 100644
index 00000000..5d8922e3
--- /dev/null
+++ b/imap/src/osdep/dos/mtestwsk.bat
@@ -0,0 +1,27 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Portable C client makefile -- MS-DOS Winsock link
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 26 June 1994
+REM Last Edited:30 August 2006
+
+link /NOD:llibce mtest.obj,mtest.exe,,cclient.lib winsock.lib llibcewq.lib libw.lib, mtest
diff --git a/imap/src/osdep/dos/mtxdos.c b/imap/src/osdep/dos/mtxdos.c
new file mode 100644
index 00000000..ad969267
--- /dev/null
+++ b/imap/src/osdep/dos/mtxdos.c
@@ -0,0 +1,875 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MTX mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 24 June 1992
+ * Last Edited: 30 August 2006
+ */
+
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <sys\stat.h>
+#include <dos.h>
+#include "rfc822.h"
+#include "dummy.h"
+#include "misc.h"
+#include "fdstring.h"
+
+/* MTX I/O stream local data */
+
+typedef struct mtx_local {
+ int fd; /* file descriptor for I/O */
+ off_t filesize; /* file size parsed */
+ unsigned char *buf; /* temporary buffer */
+} MTXLOCAL;
+
+
+/* Drive-dependent data passed to init method */
+
+typedef struct mtx_data {
+ int fd; /* file data */
+ unsigned long pos; /* initial position */
+} MTXDATA;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MTXLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *mtx_valid (char *name);
+long mtx_isvalid (char *name,char *tmp);
+void *mtx_parameters (long function,void *value);
+void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void mtx_list (MAILSTREAM *stream,char *ref,char *pat);
+void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mtx_create (MAILSTREAM *stream,char *mailbox);
+long mtx_delete (MAILSTREAM *stream,char *mailbox);
+long mtx_rename (MAILSTREAM *stream,char *old,char *newname);
+MAILSTREAM *mtx_open (MAILSTREAM *stream);
+void mtx_close (MAILSTREAM *stream,long options);
+char *mtx_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long mtx_ping (MAILSTREAM *stream);
+void mtx_check (MAILSTREAM *stream);
+long mtx_expunge (MAILSTREAM *stream,char *sequence,long options);
+long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+char *mtx_file (char *dst,char *name);
+long mtx_badname (char *tmp,char *s);
+long mtx_parse (MAILSTREAM *stream);
+void mtx_update_status (MAILSTREAM *stream,unsigned long msgno);
+unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size);
+
+/* MTX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mtxdriver = {
+ "mtx", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_LOWMEM|DR_CRLF|DR_NOSTICKY,
+ (DRIVER *) NIL, /* next driver */
+ mtx_valid, /* mailbox is valid for us */
+ mtx_parameters, /* manipulate parameters */
+ mtx_scan, /* scan mailboxes */
+ mtx_list, /* list mailboxes */
+ mtx_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ mtx_create, /* create mailbox */
+ mtx_delete, /* delete mailbox */
+ mtx_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ mtx_open, /* open mailbox */
+ mtx_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mtx_header, /* fetch message header */
+ mtx_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ mtx_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mtx_ping, /* ping mailbox to see if still alive */
+ mtx_check, /* check for new messages */
+ mtx_expunge, /* expunge deleted messages */
+ mtx_copy, /* copy messages to another mailbox */
+ mtx_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mtxproto = {&mtxdriver};
+
+/* MTX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mtx_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return mtx_isvalid (name,tmp) ? &mtxdriver : (DRIVER *) NIL;
+}
+
+
+/* MTX mail test for valid mailbox
+ * Accepts: mailbox name
+ * Returns: T if valid, NIL otherwise
+ */
+
+long mtx_isvalid (char *name,char *tmp)
+{
+ int fd;
+ long ret = NIL;
+ char *s;
+ struct stat sbuf;
+ errno = EINVAL; /* assume invalid argument */
+ /* if file, get its status */
+ if ((*name != '{') && mailboxfile (tmp,name) && !stat (tmp,&sbuf)) {
+ if (!sbuf.st_size)errno = 0;/* empty file */
+ else if ((fd = open (tmp,O_BINARY|O_RDONLY,NIL)) >= 0) {
+ memset (tmp,'\0',MAILTMPLEN);
+ if ((read (fd,tmp,64) >= 0) && (s = strchr (tmp,'\015')) &&
+ (s[1] == '\012')) { /* valid format? */
+ *s = '\0'; /* tie off header */
+ /* must begin with dd-mmm-yy" */
+ ret = (((tmp[2] == '-' && tmp[6] == '-') ||
+ (tmp[1] == '-' && tmp[5] == '-')) &&
+ (s = strchr (tmp+18,',')) && strchr (s+2,';')) ? T : NIL;
+ }
+ else errno = -1; /* bogus format */
+ close (fd); /* close the file */
+ }
+ }
+ /* in case INBOX but not mtx format */
+ else if ((errno == ENOENT) && ((name[0] == 'I') || (name[0] == 'i')) &&
+ ((name[1] == 'N') || (name[1] == 'n')) &&
+ ((name[2] == 'B') || (name[2] == 'b')) &&
+ ((name[3] == 'O') || (name[3] == 'o')) &&
+ ((name[4] == 'X') || (name[4] == 'x')) && !name[5]) errno = -1;
+ return ret; /* return what we should */
+}
+
+
+/* MTX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mtx_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* MTX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MTX mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mtx_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (stream,ref,pat);
+}
+
+
+/* MTX mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (stream,ref,pat);
+}
+
+/* MTX mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_create (MAILSTREAM *stream,char *mailbox)
+{
+ return dummy_create (stream,mailbox);
+}
+
+
+/* MTX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return dummy_delete (stream,mailbox);
+}
+
+
+/* MTX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ return dummy_rename (stream,old,newname);
+}
+
+/* MTX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mtx_open (MAILSTREAM *stream)
+{
+ long i;
+ int fd;
+ char *s;
+ char tmp[MAILTMPLEN];
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &mtxproto;
+ if (stream->local) fatal ("mtx recycle stream");
+ if (!mailboxfile (tmp,stream->mailbox))
+ return (MAILSTREAM *) mtx_badname (tmp,stream->mailbox);
+ /* open, possibly creating INBOX */
+ if (((fd = open (tmp,O_BINARY|(stream->rdonly ? O_RDONLY:O_RDWR),NIL)) < 0)&&
+ (compare_cstring (stream->mailbox,"INBOX") ||
+ ((fd = open (tmp,O_BINARY|O_RDWR|O_CREAT|O_EXCL,S_IREAD|S_IWRITE))<0))){
+ sprintf (tmp,"Can't open mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ stream->local = fs_get (sizeof (MTXLOCAL));
+ /* canonicalize the stream mailbox name */
+ fs_give ((void **) &stream->mailbox);
+ if (s = strchr ((s = strrchr (tmp,'\\')) ? s : tmp,'.')) *s = '\0';
+ stream->mailbox = cpystr (tmp);
+ LOCAL->fd = fd; /* note the file */
+ LOCAL->filesize = 0; /* initialize parsed file size */
+ LOCAL->buf = NIL; /* initially no local buffer */
+ stream->sequence++; /* bump sequence number */
+ stream->uid_validity = time (0);
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (!mtx_ping (stream)) return NIL;
+ if (!stream->nmsgs) mm_log ("Mailbox is empty",(long) NIL);
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft =
+ stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ return stream; /* return stream to caller */
+}
+
+/* MTX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mtx_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T;
+ if (options & CL_EXPUNGE) mtx_expunge (stream,NIL,NIL);
+ close (LOCAL->fd); /* close the local file */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* MTX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mtx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get to header position */
+ lseek (LOCAL->fd,mtx_hdrpos (stream,msgno,length),L_SET);
+ /* is buffer big enough? */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((size_t) *length + 1);
+ LOCAL->buf[*length] = '\0'; /* tie off string */
+ /* slurp the data */
+ read (LOCAL->fd,LOCAL->buf,(size_t) *length);
+ return LOCAL->buf;
+}
+
+
+/* MTX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: T, always
+ */
+
+long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ MESSAGECACHE *elt;
+ FDDATA d;
+ unsigned long hdrsize,hdrpos;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);/* if message not seen */
+ if (elt->seen && !(flags & FT_PEEK)) {
+ elt->seen = T; /* mark message as seen */
+ /* recalculate status */
+ mtx_update_status (stream,msgno);
+ mm_flags (stream,msgno);
+ }
+ /* get location of text data */
+ hdrpos = mtx_hdrpos (stream,msgno,&hdrsize);
+ d.fd = LOCAL->fd; /* set initial stringstruct */
+ d.pos = hdrpos + hdrsize;
+ /* flush old buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ d.chunk = LOCAL->buf = (char *) fs_get ((size_t) d.chunksize = CHUNKSIZE);
+ INIT (bs,fd_string,(void *) &d,elt->rfc822_size - hdrsize);
+ return T; /* success */
+}
+
+/* MTX mail per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ /* recalculate status */
+ mtx_update_status (stream,elt->msgno);
+}
+
+
+/* MTX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long mtx_ping (MAILSTREAM *stream)
+{
+ /* punt if stream no longer alive */
+ if (!(stream && LOCAL)) return NIL;
+ /* parse mailbox, punt if parse dies */
+ return (mtx_parse (stream)) ? T : NIL;
+}
+
+
+/* MTX mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void mtx_check (MAILSTREAM *stream)
+{
+ unsigned long i = 1;
+ if (mtx_ping (stream)) { /* ping mailbox */
+ /* get new message status */
+ while (i <= stream->nmsgs) mail_elt (stream,i++);
+ mm_log ("Check completed",(long) NIL);
+ }
+}
+
+/* MTX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long mtx_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ unsigned long i = 1;
+ unsigned long j,k,m,recent;
+ unsigned long n = 0;
+ unsigned long delta = 0;
+ MESSAGECACHE *elt;
+ char tmp[MAILTMPLEN];
+ if (!(ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ mtx_ping (stream))); /* parse sequence if given, ping stream */
+ else if (stream->rdonly) mm_log ("Expunge ignored on readonly mailbox",WARN);
+ else {
+ mm_critical (stream); /* go critical */
+ recent = stream->recent; /* get recent now that pinged */
+ while (i <= stream->nmsgs) {/* for each message */
+ elt = mail_elt (stream,i);/* get cache element */
+ /* number of bytes to smash or preserve */
+ k = elt->private.special.text.size + elt->rfc822_size;
+ /* if need to expunge this message */
+ if (elt->deleted && (sequence ? elt->sequence : T)) {
+ /* if recent, note one less recent message */
+ if (elt->recent) --recent;
+ delta += k; /* number of bytes to delete */
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more deleted message */
+ }
+ else if (i++ && delta) { /* preserved message */
+ /* first byte to preserve */
+ j = elt->private.special.offset;
+ do { /* read from source position */
+ m = min (k,(unsigned long) MAILTMPLEN);
+ lseek (LOCAL->fd,j,SEEK_SET);
+ read (LOCAL->fd,tmp,(size_t) m);
+ /* write to destination position */
+ lseek (LOCAL->fd,j - delta,SEEK_SET);
+ write (LOCAL->fd,tmp,(size_t) m);
+ j += m; /* next chunk, perhaps */
+ } while (k -= m); /* until done */
+ elt->private.special.offset -= delta;
+ }
+ }
+ if (n) { /* truncate file after last message */
+ chsize (LOCAL->fd,LOCAL->filesize -= delta);
+ sprintf (tmp,"Expunged %ld messages",n);
+ mm_log (tmp,(long) NIL); /* output the news */
+ }
+ else mm_log ("No messages deleted, so no update needed",(long) NIL);
+ mm_nocritical (stream); /* release critical */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ }
+ return ret;
+}
+
+/* MTX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ char tmp[MAILTMPLEN];
+ struct stat sbuf;
+ MESSAGECACHE *elt;
+ unsigned long i,j,k;
+ long ret = LONGT;
+ int fd;
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ if (!mtx_isvalid (mailbox,tmp)) switch (errno) {
+ case ENOENT: /* no such file? */
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",
+ (long) NIL);
+ return NIL;
+ case 0: /* merely empty file? */
+ break;
+ case EINVAL: /* name is bogus */
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ return mtx_badname (tmp,mailbox);
+ default: /* file exists, but not valid format */
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (tmp,"Not a MTX-format mailbox: %s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* open the destination */
+ if (!mailboxfile (tmp,mailbox) ||
+ (fd = open (tmp,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,
+ S_IREAD|S_IWRITE)) < 0) {
+ sprintf (tmp,"Unable to open copy mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+
+ mm_critical (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ /* for each requested message */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,SEEK_SET);
+ /* number of bytes to copy */
+ k = elt->private.special.text.size + elt->rfc822_size;
+ do { /* read from source position */
+ j = min (k,(long) MAILTMPLEN);
+ read (LOCAL->fd,tmp,(size_t) j);
+ if (write (fd,tmp,(size_t) j) < 0) {
+ sprintf (tmp,"Unable to write message: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ chsize (fd,sbuf.st_size);
+ j = k;
+ ret = NIL; /* note error */
+ break;
+ }
+ } while (k -= j); /* until done */
+ }
+ close (fd); /* close the file */
+ mm_nocritical (stream); /* release critical */
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE)) for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ elt->deleted = T; /* mark message deleted */
+ /* recalculate status */
+ mtx_update_status (stream,i);
+ }
+ if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
+ mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN);
+ return ret;
+}
+
+/* MTX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd,ld,c;
+ char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN];
+ FILE *df;
+ MESSAGECACHE elt;
+ long f;
+ unsigned long i,uf;
+ STRING *message;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = &mtxproto;
+ /* make sure valid mailbox */
+ if (!mtx_isvalid (mailbox,tmp)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (((mailbox[0] == 'I') || (mailbox[0] == 'i')) &&
+ ((mailbox[1] == 'N') || (mailbox[1] == 'n')) &&
+ ((mailbox[2] == 'B') || (mailbox[2] == 'b')) &&
+ ((mailbox[3] == 'O') || (mailbox[3] == 'o')) &&
+ ((mailbox[4] == 'X') || (mailbox[4] == 'x')) && !mailbox[5])
+ dummy_create (NIL,"INBOX.MTX");
+ else {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* falls through */
+ case 0: /* merely empty file? */
+ break;
+ case EINVAL:
+ return mtx_badname (tmp,mailbox);
+ default:
+ sprintf (tmp,"Not a MTX-format mailbox: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get first message */
+ if (!(*af) (stream,data,&flags,&date,&message)) return NIL;
+ /* open destination mailbox */
+ if (!mailboxfile (file,mailbox) ||
+ ((fd = open (file,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,
+ S_IREAD|S_IWRITE)) < 0) || !(df = fdopen (fd,"ab"))) {
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ mm_critical (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+
+ errno = 0;
+ do { /* parse flags */
+ if (!SIZE (message)) { /* guard against zero-length */
+ mm_log ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ f = mail_parse_flags (stream,flags,&i);
+ /* reverse bits (dontcha wish we had CIRC?) */
+ for (uf = 0; i; uf |= 1 << (29 - find_rightmost_bit (&i)));
+ if (date) { /* parse date if given */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ mm_log (tmp,ERROR);
+ ret = NIL; /* mark failure */
+ break;
+ }
+ mail_date (tmp,&elt); /* write preseved date */
+ }
+ else internal_date (tmp); /* get current date in IMAP format */
+ /* write header */
+ if (fprintf (df,"%s,%lu;%010lo%02lo\015\012",tmp,i = SIZE (message),uf,
+ (unsigned long) f) < 0) ret = NIL;
+ else { /* write message */
+ if (i) do c = 0xff & SNX (message);
+ while ((putc (c,df) != EOF) && --i);
+ /* get next message */
+ if (i || !(*af) (stream,data,&flags,&date,&message)) ret = NIL;
+ }
+ } while (ret && message);
+ /* revert file if failure */
+ if (!ret || (fflush (df) == EOF)) {
+ chsize (fd,sbuf.st_size); /* revert file */
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ if (errno) {
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+ ret = NIL;
+ }
+ fclose (df); /* close the file */
+ mm_nocritical (stream); /* release critical */
+ if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
+ mm_log ("Can not return meaningful APPENDUID with this mailbox format",
+ WARN);
+ return ret;
+}
+
+
+/* Return bad file name error message
+ * Accepts: temporary buffer
+ * file name
+ * Returns: long NIL always
+ */
+
+long mtx_badname (char *tmp,char *s)
+{
+ sprintf (tmp,"Invalid mailbox name: %s",s);
+ mm_log (tmp,ERROR);
+ return (long) NIL;
+}
+
+/* Parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long mtx_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = NIL;
+ unsigned char *s,*t,*x,lbuf[65];
+ char tmp[MAILTMPLEN];
+ long i;
+ long curpos = LOCAL->filesize;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %ld to %ld!",curpos,sbuf.st_size);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ /* while there is stuff to parse */
+ while (i = sbuf.st_size - curpos) {
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,SEEK_SET);
+ if ((i = read (LOCAL->fd,lbuf,64)) <= 0) {
+ sprintf (tmp,"Unable to read internal header at %ld, size = %ld: %s",
+ curpos,sbuf.st_size,i ? strerror (errno) : "no data read");
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ lbuf[i] = '\0'; /* tie off buffer just in case */
+ if (!((s = strchr (lbuf,'\015')) && (s[1] == '\012'))) {
+ sprintf (tmp,"Unable to find end of line at %ld in %ld bytes, text: %s",
+ curpos,i,(char *) lbuf);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ *s = '\0'; /* tie off header line */
+ i = (s + 2) - lbuf; /* note start of text offset */
+ if (!((s = strchr (lbuf,',')) && (t = strchr (s+1,';')))) {
+ sprintf (tmp,"Unable to parse internal header at %ld: %s",curpos,
+ (char *) lbuf);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+
+ *s++ = '\0'; *t++ = '\0'; /* tie off fields */
+ /* intantiate an elt for this message */
+ (elt = mail_elt (stream,++nmsgs))->valid = T;
+ elt->private.uid = ++stream->uid_last;
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* as well as offset from header of message */
+ elt->private.special.text.size = i;
+ /* header size not known yet */
+ elt->private.msg.header.text.size = 0;
+ /* parse the header components */
+ if (!(mail_parse_date (elt,lbuf) &&
+ (elt->rfc822_size = strtol (x = s,(char **) &s,10)) && (!(s && *s))&&
+ isdigit (t[0]) && isdigit (t[1]) && isdigit (t[2]) &&
+ isdigit (t[3]) && isdigit (t[4]) && isdigit (t[5]) &&
+ isdigit (t[6]) && isdigit (t[7]) && isdigit (t[8]) &&
+ isdigit (t[9]) && isdigit (t[10]) && isdigit (t[11]) && !t[12])) {
+ sprintf (tmp,"Unable to parse internal header elements at %ld: %s,%s;%s",
+ curpos,(char *) lbuf,(char *) x,(char *) t);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ /* update current position to next header */
+ curpos += i + elt->rfc822_size;
+ /* calculate system flags */
+ if ((i = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T;
+ if (i & fDELETED) elt->deleted = T;
+ if (i & fFLAGGED) elt->flagged = T;
+ if (i & fANSWERED) elt->answered = T;
+ if (i & fDRAFT) elt->draft = T;
+ if (curpos > sbuf.st_size) {
+ sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
+ elt->private.special.offset,curpos,sbuf.st_size);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ }
+ /* update parsed file size */
+ LOCAL->filesize = sbuf.st_size;
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return T; /* return the winnage */
+}
+
+/* Update status string
+ * Accepts: MAIL stream
+ * message number
+ */
+
+void mtx_update_status (MAILSTREAM *stream,unsigned long msgno)
+{
+ char tmp[MAILTMPLEN];
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ unsigned long j,k = 0;
+ /* not if readonly you don't */
+ if (stream->rdonly || !elt->valid) return;
+ j = elt->user_flags; /* get user flags */
+ /* reverse bits (dontcha wish we had CIRC?) */
+ while (j) k |= 1 << 29 - find_rightmost_bit (&j);
+ sprintf (tmp,"%010lo%02o",k, /* print new flag string */
+ fOLD + (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft));
+ /* get to that place in the file */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 14,SEEK_SET);
+ write (LOCAL->fd,tmp,12); /* write new flags */
+}
+
+/* MTX locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * Returns: position of header in file
+ */
+
+unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size)
+{
+ unsigned long siz;
+ size_t i = 0;
+ int q = 0;
+ char *s,tmp[MAILTMPLEN];
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ long pos = elt->private.special.offset + elt->private.special.text.size;
+ /* is header size known? */
+ if (!(*size = elt->private.msg.header.text.size)) {
+ /* get to header position */
+ lseek (LOCAL->fd,pos,SEEK_SET);
+ /* search message for CRLF CRLF */
+ for (siz = 1; siz <= elt->rfc822_size; siz++) {
+ if (!i && /* buffer empty? */
+ (read (LOCAL->fd,s = tmp,
+ i = (size_t) min(elt->rfc822_size-siz,(long)MAILTMPLEN))<= 0))
+ return pos;
+ else i--;
+ switch (q) { /* sniff at buffer */
+ case 0: /* first character */
+ q = (*s++ == '\015') ? 1 : 0;
+ break;
+ case 1: /* second character */
+ q = (*s++ == '\012') ? 2 : 0;
+ break;
+ case 2: /* third character */
+ q = (*s++ == '\015') ? 3 : 0;
+ break;
+ case 3: /* fourth character */
+ if (*s++ == '\012') { /* have the sequence? */
+ /* yes, note for later */
+ elt->private.msg.header.text.size = (*size = siz);
+ return pos; /* return to caller */
+ }
+ q = 0; /* lost... */
+ break;
+ }
+ }
+ }
+ return pos; /* have position */
+}
diff --git a/imap/src/osdep/dos/nl_dos.c b/imap/src/osdep/dos/nl_dos.c
new file mode 100644
index 00000000..47cb7f0a
--- /dev/null
+++ b/imap/src/osdep/dos/nl_dos.c
@@ -0,0 +1,61 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Windows/TOPS-20 newline routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Copy string with CRLF newlines
+ * Accepts: destination string
+ * pointer to size of destination string buffer
+ * source string
+ * length of source string
+ * Returns: length of copied string
+ */
+
+unsigned long strcrlfcpy (unsigned char **dst,unsigned long *dstl,
+ unsigned char *src,unsigned long srcl)
+{
+ /* flush destination buffer if too small */
+ if (*dst && (srcl > *dstl)) fs_give ((void **) dst);
+ if (!*dst) { /* make a new buffer if needed */
+ *dst = (char *) fs_get ((size_t) (*dstl = srcl) + 1);
+ if (dstl) *dstl = srcl; /* return new buffer length to main program */
+ }
+ /* copy strings */
+ if (srcl) memcpy (*dst,src,(size_t) srcl);
+ *(*dst + srcl) = '\0'; /* tie off destination */
+ return srcl; /* return length */
+}
+
+
+/* Length of string after strcrlfcpy applied
+ * Accepts: source string
+ * Returns: length of string
+ */
+
+unsigned long strcrlflen (STRING *s)
+{
+ return SIZE (s); /* no-brainer on DOS! */
+}
diff --git a/imap/src/osdep/dos/os_dbw.c b/imap/src/osdep/dos/os_dbw.c
new file mode 100644
index 00000000..e28e1ab0
--- /dev/null
+++ b/imap/src/osdep/dos/os_dbw.c
@@ -0,0 +1,91 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- MS-DOS (B&W) version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+/* Private function prototypes */
+
+#include "tcp_dos.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys\stat.h>
+#include <sys\timeb.h>
+#include "misc.h"
+#include "stdlib.h"
+#include "bwtcp.h"
+
+
+#include "fs_dos.c"
+#include "ftl_dos.c"
+#include "nl_dos.c"
+#include "env_dos.c"
+#undef write
+#define read soread
+#define write sowrite
+#define close soclose
+#include "tcp_dos.c"
+
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost (void)
+{
+ char *s;
+ if (!myLocalHost) { /* known yet? */
+ /* get local host name from DISPLAY env var */
+ if (!((s = getenv ("DISPLAY")) || (s = getenv ("display")))) {
+ mm_log ("Environment variable 'DISPLAY' is not set", ERROR);
+ s = "random-pc";
+ }
+ myLocalHost = cpystr (s);
+ }
+ return myLocalHost;
+}
+
+
+/* Look up host address
+ * Accepts: pointer to pointer to host name
+ * socket address block
+ * Returns: non-zero with host address in socket, official host name in host;
+ * else NIL
+ */
+
+long lookuphost (char **host,struct sockaddr_in *sin)
+{
+ char *s = *host; /* in case of error */
+ sin->sin_addr.s_addr = rhost (host);
+ if (sin->sin_addr.s_addr == -1) {
+ *host = s; /* error, restore old host name */
+ return NIL;
+ }
+ *host = cpystr (*host); /* make permanent copy of name */
+ return T; /* success */
+}
diff --git a/imap/src/osdep/dos/os_dbw.h b/imap/src/osdep/dos/os_dbw.h
new file mode 100644
index 00000000..f22cae6f
--- /dev/null
+++ b/imap/src/osdep/dos/os_dbw.h
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- DOS (B&W/Novell) version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#define INADEQUATE_MEMORY
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys\types.h>
+#include <io.h>
+
+#define gethostid clock
+
+#include "env_dos.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/dos/os_dnf.c b/imap/src/osdep/dos/os_dnf.c
new file mode 100644
index 00000000..dcc3ea64
--- /dev/null
+++ b/imap/src/osdep/dos/os_dnf.c
@@ -0,0 +1,95 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- MS-DOS (PC-NFS) version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+/* Private function prototypes */
+
+#include "tcp_dos.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys\stat.h>
+#include <sys\timeb.h>
+#include <sys\nfs_time.h>
+#include <sys\tk_types.h>
+#include <sys\socket.h>
+#include <netinet\in.h>
+#include <tk_errno.h>
+#include <sys\uio.h>
+#include <netdb.h>
+#include "misc.h"
+
+
+#include "fs_dos.c"
+#include "ftl_dos.c"
+#include "nl_dos.c"
+#include "env_dos.c"
+#undef write
+#include "tcp_dos.c"
+
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost (void)
+{
+ if (!myLocalHost) { /* known yet? */
+ char *s,tmp[MAILTMPLEN];
+ unsigned long myip;
+ /* see if known host name */
+ if (!gethostname (tmp,MAILTMPLEN-1)) s = tmp;
+ /* no, try host address */
+ else if (get_myipaddr ((char *) &myip))
+ sprintf (s = tmp,"[%s]",inet_ntoa (myip));
+ else s = "random-pc"; /* say what? */
+ myLocalHost = cpystr (s); /* record for subsequent use */
+ }
+ return myLocalHost;
+}
+
+
+/* Look up host address
+ * Accepts: pointer to pointer to host name
+ * socket address block
+ * Returns: non-zero with host address in socket, official host name in host;
+ * else NIL
+ */
+
+long lookuphost (char **host,struct sockaddr_in *sin)
+{
+ long ret = -1;
+ char tmp[MAILTMPLEN];
+ struct hostent *hn = gethostbyname (lcase (strcpy (tmp,*host)));
+ if (!hn) return NIL; /* got a host name? */
+ *host = cpystr (hn->h_name); /* set official name */
+ /* copy host addresses */
+ memcpy (&sin->sin_addr,hn->h_addr,hn->h_length);
+ return T;
+}
diff --git a/imap/src/osdep/dos/os_dnf.h b/imap/src/osdep/dos/os_dnf.h
new file mode 100644
index 00000000..3a832217
--- /dev/null
+++ b/imap/src/osdep/dos/os_dnf.h
@@ -0,0 +1,44 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- DOS (PC-NFS) version
+ *
+ * Author: Mike Seibel from Novell version by Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MikeS@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#define INADEQUATE_MEMORY
+
+#include <tklib.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys\types.h>
+#include <io.h>
+
+#define gethostid clock
+
+#include "env_dos.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/dos/os_dnv.c b/imap/src/osdep/dos/os_dnv.c
new file mode 100644
index 00000000..e0341764
--- /dev/null
+++ b/imap/src/osdep/dos/os_dnv.c
@@ -0,0 +1,95 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- MS-DOS (Novell) version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+/* Private function prototypes */
+
+#include "tcp_dos.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys\stat.h>
+#include <sys\timeb.h>
+#include <sys\socket.h>
+#include <netinet\in.h>
+#include <netdb.h>
+#include "misc.h"
+
+
+#include "fs_dos.c"
+#include "ftl_dos.c"
+#include "nl_dos.c"
+#include "env_dos.c"
+#undef write
+#define read soread
+#define write sowrite
+#define close soclose
+#include "tcp_dos.c"
+
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost (void)
+{
+ if (!myLocalHost) { /* known yet? */
+ char *s,tmp[MAILTMPLEN];
+ struct hostent *he;
+ struct in_addr in;
+ /* could we get local id? */
+ if ((in.s_addr = getmyipaddr ()) != -1) {
+ if (he = gethostbyaddr ((char *) &in.s_addr,4,PF_INET)) s = he->h_name;
+ else sprintf (s = tmp,"[%s]",inet_ntoa (in));
+ }
+ else s = "random-pc"; /* say what? */
+ myLocalHost = cpystr (s); /* record for subsequent use */
+ }
+ return myLocalHost;
+}
+
+
+/* Look up host address
+ * Accepts: pointer to pointer to host name
+ * socket address block
+ * Returns: non-zero with host address in socket, official host name in host;
+ * else NIL
+ */
+
+long lookuphost (char **host,struct sockaddr_in *sin)
+{
+ char *s = *host; /* in case of error */
+ sin->sin_addr.s_addr = rhost (host);
+ if (sin->sin_addr.s_addr == -1) {
+ *host = s; /* error, restore old host name */
+ return NIL;
+ }
+ *host = cpystr (*host); /* make permanent copy of name */
+ return T; /* success */
+}
diff --git a/imap/src/osdep/dos/os_dnv.h b/imap/src/osdep/dos/os_dnv.h
new file mode 100644
index 00000000..f22cae6f
--- /dev/null
+++ b/imap/src/osdep/dos/os_dnv.h
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- DOS (B&W/Novell) version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#define INADEQUATE_MEMORY
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys\types.h>
+#include <io.h>
+
+#define gethostid clock
+
+#include "env_dos.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/dos/os_dpc.c b/imap/src/osdep/dos/os_dpc.c
new file mode 100644
index 00000000..eece19d7
--- /dev/null
+++ b/imap/src/osdep/dos/os_dpc.c
@@ -0,0 +1,102 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- MS-DOS (PC/TCP) version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+/* Private function prototypes */
+
+#include "tcp_dos.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys\stat.h>
+#include <sys\timeb.h>
+#include <4bsddefs.h>
+#include <sys\socket.h>
+#include <errno.h>
+#include <arpa\inet.h>
+#include <netinet\in.h>
+#include <netdb.h>
+#include "misc.h"
+
+
+#include "fs_dos.c"
+#include "ftl_dos.c"
+#include "nl_dos.c"
+#include "env_dos.c"
+#undef write
+#include "tcp_dos.c"
+
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost (void)
+{
+ if (!myLocalHost) { /* known yet */
+ char *s,tmp[MAILTMPLEN];
+ long myip;
+ /* see if known host name */
+ if (!gethostname (tmp,MAILTMPLEN-1)) s = tmp;
+ /* no, try IP address */
+ else if (myip = gethostid ()) {
+ struct in_addr in;
+ in.s_addr = myip;
+ sprintf (s = tmp,"[%s]",inet_ntoa (in));
+ }
+ /* older kernel, look harder. */
+ else if (getconf ("ifcust","ip-address",tmp+1,MAILTMPLEN-2)) {
+ *(s = tmp) = '['; /* wrap the brackets around it */
+ strcat (tmp,"]");
+ }
+ else s = "random-pc"; /* say what? */
+ myLocalHost = cpystr (s); /* record for subsequent use */
+ }
+ return myLocalHost;
+}
+
+
+/* Look up host address
+ * Accepts: pointer to pointer to host name
+ * socket address block
+ * Returns: non-zero with host address in socket, official host name in host;
+ * else NIL
+ */
+
+long lookuphost (char **host,struct sockaddr_in *sin)
+{
+ long ret = -1;
+ char tmp[MAILTMPLEN];
+ struct hostent *hn = gethostbyname (lcase (strcpy (tmp,*host)));
+ if (!hn) return NIL; /* got a host name? */
+ *host = cpystr (hn->h_name); /* set official name */
+ /* copy host addresses */
+ memcpy (&sin->sin_addr,hn->h_addr,hn->h_length);
+ return T;
+}
diff --git a/imap/src/osdep/dos/os_dpc.h b/imap/src/osdep/dos/os_dpc.h
new file mode 100644
index 00000000..5de3e637
--- /dev/null
+++ b/imap/src/osdep/dos/os_dpc.h
@@ -0,0 +1,41 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- DOS (PC/TCP) version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#define INADEQUATE_MEMORY
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys\types.h>
+#include <io.h>
+
+#include "env_dos.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/dos/os_dwa.c b/imap/src/osdep/dos/os_dwa.c
new file mode 100644
index 00000000..36e5b147
--- /dev/null
+++ b/imap/src/osdep/dos/os_dwa.c
@@ -0,0 +1,72 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- DOS (Waterloo) version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include <tcp.h> /* must be before TCPSTREAM definition */
+#include "tcp_dwa.h" /* must be before osdep include our tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys\stat.h>
+#include <sys\timeb.h>
+#include "misc.h"
+
+/* Undo compatibility definition */
+
+#undef tcp_open
+
+#include "fs_dos.c"
+#include "ftl_dos.c"
+#include "nl_dos.c"
+#include "env_dos.c"
+#include "tcp_dwa.c"
+
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost (void)
+{
+ if (!myLocalHost) {
+ char *s,hname[32],tmp[MAILTMPLEN];
+ long myip;
+
+ if (!sock_initted++) sock_init();
+ tcp_cbrk (0x01); /* turn off ctrl-break catching */
+ /*
+ * haven't discovered a way to find out the local host's
+ * name with wattcp yet.
+ */
+ if (myip = gethostid ()) sprintf (s = tmp,"[%s]",inet_ntoa (hname,myip));
+ else s = "random-pc";
+ myLocalHost = cpystr (s);
+ }
+ return myLocalHost;
+}
diff --git a/imap/src/osdep/dos/os_dwa.h b/imap/src/osdep/dos/os_dwa.h
new file mode 100644
index 00000000..f70fa31b
--- /dev/null
+++ b/imap/src/osdep/dos/os_dwa.h
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- DOS (Waterloo) version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#define INADEQUATE_MEMORY
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys\types.h>
+#include <io.h>
+
+#define tcp_open TCP_open
+
+#include "env_dos.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/dos/os_wsk.c b/imap/src/osdep/dos/os_wsk.c
new file mode 100644
index 00000000..8627ef89
--- /dev/null
+++ b/imap/src/osdep/dos/os_wsk.c
@@ -0,0 +1,45 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Winsock version
+ *
+ * Author: Mike Seibel from Unix version by Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MikeS@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_wsk.h" /* must be before osdep includes tcp.h */
+#undef ERROR /* quell conflicting def warning */
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys\stat.h>
+#include <sys\timeb.h>
+#include "misc.h"
+
+
+#include "fs_dos.c"
+#include "ftl_dos.c"
+#include "nl_dos.c"
+#include "env_dos.c"
+#include "tcp_wsk.c"
diff --git a/imap/src/osdep/dos/os_wsk.h b/imap/src/osdep/dos/os_wsk.h
new file mode 100644
index 00000000..10213cef
--- /dev/null
+++ b/imap/src/osdep/dos/os_wsk.h
@@ -0,0 +1,48 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- 16-bit Winsock version
+ *
+ * Author: Mike Seibel from Novell version by Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MikeS@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#define INADEQUATE_MEMORY
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys\types.h>
+#include <io.h>
+
+#define gethostid clock
+#define WSA_VERSION ((1 << 8) | 1)
+
+#include "env_dos.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+
+
+#undef noErr
+#undef MAC
diff --git a/imap/src/osdep/dos/pmatch.c b/imap/src/osdep/dos/pmatch.c
new file mode 100644
index 00000000..95a0bb86
--- /dev/null
+++ b/imap/src/osdep/dos/pmatch.c
@@ -0,0 +1,89 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: IMAP Wildcard Matching Routines (case-independent)
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 June 2000
+ * Last Edited: 30 August 2006
+ */
+
+/* Wildcard pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if pattern matches base, else NIL
+ */
+
+long pmatch_full (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ /* % at end, OK if no inferiors */
+ if (!pat[1]) return (delim && strchr (s,delim)) ? NIL : T;
+ /* scan remainder of string until delimiter */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while ((*s != delim) && *s++);
+ break;
+ case '*': /* match 0 or more characters */
+ if (!pat[1]) return T; /* * at end, unconditional match */
+ /* scan remainder of string */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while (*s++);
+ break;
+ case '\0': /* end of pattern */
+ return *s ? NIL : T; /* success if also end of base */
+ default: /* match this character */
+ return compare_uchar (*pat,*s) ? NIL : pmatch_full (s+1,pat+1,delim);
+ }
+ return NIL;
+}
+
+/* Directory pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if base is a matching directory of pattern, else NIL
+ */
+
+long dmatch (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ if (!*s) return T; /* end of base means have a subset match */
+ if (!*++pat) return NIL; /* % at end, no inferiors permitted */
+ /* scan remainder of string until delimiter */
+ do if (dmatch (s,pat,delim)) return T;
+ while ((*s != delim) && *s++);
+ if (*s && !s[1]) return T; /* ends with delimiter, must be subset */
+ return dmatch (s,pat,delim);/* do new scan */
+ case '*': /* match 0 or more characters */
+ return T; /* unconditional match */
+ case '\0': /* end of pattern */
+ break;
+ default: /* match this character */
+ if (*s) return compare_uchar (*pat,*s) ? NIL : dmatch (s+1,pat+1,delim);
+ /* end of base, return if at delimiter */
+ else if (*pat == delim) return T;
+ break;
+ }
+ return NIL;
+}
diff --git a/imap/src/osdep/dos/tcp_dos.c b/imap/src/osdep/dos/tcp_dos.c
new file mode 100644
index 00000000..062f260c
--- /dev/null
+++ b/imap/src/osdep/dos/tcp_dos.c
@@ -0,0 +1,434 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MS-DOS TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 13 January 2008
+ */
+
+static tcptimeout_t tmoh = NIL; /* TCP timeout handler routine */
+static long ttmo_read = 0; /* TCP timeouts, in seconds */
+static long ttmo_write = 0;
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd);
+
+/* TCP/IP manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tcp_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_TIMEOUT:
+ tmoh = (tcptimeout_t) value;
+ case GET_TIMEOUT:
+ ret = (void *) tmoh;
+ break;
+ case SET_READTIMEOUT:
+ ttmo_read = (long) value;
+ case GET_READTIMEOUT:
+ ret = (void *) ttmo_read;
+ break;
+ case SET_WRITETIMEOUT:
+ ttmo_write = (long) value;
+ case GET_WRITETIMEOUT:
+ ret = (void *) ttmo_write;
+ break;
+ }
+ return ret;
+}
+
+/* TCP/IP open
+ * Accepts: host name
+ * contact service name
+ * contact port number
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = NIL;
+ struct sockaddr_in sin;
+ int sock;
+ char *s,tmp[MAILTMPLEN];
+ port &= 0xffff; /* erase flags */
+ /* The domain literal form is used (rather than simply the dotted decimal
+ as with other Unix programs) because it has to be a valid "host name"
+ in mailsystem terminology. */
+ sin.sin_family = AF_INET; /* family is always Internet */
+ /* look like domain literal? */
+ if (host[0] == '[' && host[(strlen (host))-1] == ']') {
+ strcpy (tmp,host+1); /* yes, copy number part */
+ tmp[strlen (tmp)-1] = '\0';
+ if ((sin.sin_addr.s_addr = inet_addr (tmp)) == -1) {
+ sprintf (tmp,"Bad format domain-literal: %.80s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ }
+ /* look up host name */
+ else if (!lookuphost (&host,&sin)) {
+ sprintf (tmp,"Host not found: %s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+
+ /* copy port number in network format */
+ if (!(sin.sin_port = htons (port))) fatal ("Bad port argument to tcp_open");
+ /* get a TCP stream */
+ if ((sock = socket (sin.sin_family,SOCK_STREAM,0)) < 0) {
+ sprintf (tmp,"Unable to create TCP socket (%d)",errno);
+ mm_log (tmp,ERROR);
+ fs_give ((void **) &host);
+ return NIL;
+ }
+#if 0
+ /* needed? */
+ else if (sock >= FD_SETSIZE) {/* unselectable sockets are useless */
+ sprintf (tmp,"Unable to create selectable TCP socket (%d >= %d)",
+ sock,FD_SETSIZE);
+ close (sock);
+ errno = ENOBUFS; /* just in case */
+ return NIL;
+ }
+#endif
+ /* open connection */
+ if (connect (sock,(struct sockaddr *) &sin,sizeof (sin)) < 0) {
+ switch (errno) { /* analyze error */
+ case ECONNREFUSED:
+ s = "Refused";
+ break;
+ case ENOBUFS:
+ s = "Insufficient system resources";
+ break;
+ case ETIMEDOUT:
+ s = "Timed out";
+ break;
+ default:
+ s = "Unknown error";
+ break;
+ }
+ sprintf (tmp,"Can't connect to %.80s,%ld: %s (%d)",host,port,s,errno);
+ mm_log (tmp,ERROR);
+ close (sock);
+ fs_give ((void **) &host);
+ return NIL;
+ }
+ /* create TCP/IP stream */
+ stream = (TCPSTREAM *) fs_get (sizeof (TCPSTREAM));
+ stream->host = host; /* official host name */
+ stream->localhost = cpystr (mylocalhost ());
+ stream->port = port; /* port number */
+ stream->tcps = sock; /* init socket */
+ stream->ictr = 0; /* init input counter */
+ return stream; /* return success */
+}
+
+/* TCP/IP authenticated open
+ * Accepts: NETMBX specifier
+ * service name
+ * returned user name buffer
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL; /* always NIL on DOS */
+}
+
+/* TCP receive line
+ * Accepts: TCP stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *tcp_getline (TCPSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = tcp_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = tcp_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* TCP receive line or partial line
+ * Accepts: TCP stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!tcp_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!tcp_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* TCP/IP receive buffer
+ * Accepts: TCP/IP stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *buffer)
+{
+ unsigned long n;
+ char *bufptr = buffer;
+ while (size > 0) { /* until request satisfied */
+ if (!tcp_getdata (stream)) return NIL;
+ n = min (size,stream->ictr);/* number of bytes to transfer */
+ /* do the copy */
+ memcpy (bufptr,stream->iptr,n);
+ bufptr += n; /* update pointer */
+ stream->iptr +=n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -=n;
+ }
+ bufptr[0] = '\0'; /* tie off string */
+ return T;
+}
+
+/* TCP/IP receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getdata (TCPSTREAM *stream)
+{
+ int i;
+ fd_set fds,efds;
+ struct timeval tmo;
+ time_t t = time (0);
+ if (stream->tcps < 0) return NIL;
+ while (stream->ictr < 1) { /* if nothing in the buffer */
+ time_t tl = time (0); /* start of request */
+ tmo.tv_sec = ttmo_read; /* read timeout */
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ FD_SET (stream->tcps,&fds);/* set bit in selection vector */
+ FD_SET(stream->tcps,&efds);/* set bit in error selection vector */
+ errno = NIL; /* block and read */
+ while (((i = select (stream->tcps+1,&fds,0,&efds,ttmo_read ? &tmo : 0))<0)
+ && (errno == EINTR));
+ if (!i) { /* timeout? */
+ time_t tc = time (0);
+ if (tmoh && ((*tmoh) (tc - t,tc - tl))) continue;
+ else return tcp_abort (stream);
+ }
+ else if (i < 0) return tcp_abort (stream);
+ while (((i = read (stream->tcps,stream->ibuf,BUFLEN)) < 0) &&
+ (errno == EINTR));
+ if (i < 1) return tcp_abort (stream);
+ stream->iptr = stream->ibuf;/* point at TCP buffer */
+ stream->ictr = i; /* set new byte count */
+ }
+ return T;
+}
+
+/* TCP/IP send string as record
+ * Accepts: TCP/IP stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long tcp_soutr (TCPSTREAM *stream,char *string)
+{
+ return tcp_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* TCP/IP send string
+ * Accepts: TCP/IP stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
+{
+ int i;
+ fd_set fds;
+ struct timeval tmo;
+ time_t t = time (0);
+ if (stream->tcps < 0) return NIL;
+ while (size > 0) { /* until request satisfied */
+ time_t tl = time (0); /* start of request */
+ tmo.tv_sec = ttmo_write; /* write timeout */
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_SET (stream->tcps,&fds);/* set bit in selection vector */
+ errno = NIL; /* block and write */
+ while (((i = select (stream->tcps+1,0,&fds,0,ttmo_write ? &tmo : 0)) < 0)
+ && (errno == EINTR));
+ if (!i) { /* timeout? */
+ time_t tc = time (0);
+ if (tmoh && ((*tmoh) (tc - t,tc - tl))) continue;
+ else return tcp_abort (stream);
+ }
+ else if (i < 0) return tcp_abort (stream);
+ while (((i = write (stream->tcps,string,size)) < 0) && (errno == EINTR));
+ if (i < 0) return tcp_abort (stream);
+ size -= i; /* how much we sent */
+ string += i;
+ }
+ return T; /* all done */
+}
+
+/* TCP/IP close
+ * Accepts: TCP/IP stream
+ */
+
+void tcp_close (TCPSTREAM *stream)
+{
+ tcp_abort (stream); /* nuke the socket */
+ /* flush host names */
+ fs_give ((void **) &stream->host);
+ fs_give ((void **) &stream->localhost);
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* TCP/IP abort stream
+ * Accepts: TCP/IP stream
+ * Returns: NIL always
+ */
+
+long tcp_abort (TCPSTREAM *stream)
+{
+ if (stream->tcps >= 0) close (stream->tcps);
+ stream->tcps = -1;
+ return NIL;
+}
+
+/* TCP/IP get host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_host (TCPSTREAM *stream)
+{
+ return stream->host; /* return host name */
+}
+
+
+/* TCP/IP get remote host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_remotehost (TCPSTREAM *stream)
+{
+ return stream->host; /* all we can do for now */
+}
+
+
+/* TCP/IP return port for this stream
+ * Accepts: TCP/IP stream
+ * Returns: port number for this stream
+ */
+
+unsigned long tcp_port (TCPSTREAM *stream)
+{
+ return stream->port; /* return port number */
+}
+
+
+/* TCP/IP get local host name
+ * Accepts: TCP/IP stream
+ * Returns: local host name
+ */
+
+char *tcp_localhost (TCPSTREAM *stream)
+{
+ return stream->localhost; /* return local host name */
+}
+
+
+/* TCP/IP return canonical form of host name
+ * Accepts: host name
+ * Returns: canonical form of host name
+ */
+
+char *tcp_canonical (char *name)
+{
+ return name;
+}
+
+
+/* TCP/IP get client host name (server calls only)
+ * Returns: client host name
+ */
+
+char *tcp_clienthost ()
+{
+ return "UNKNOWN";
+}
diff --git a/imap/src/osdep/dos/tcp_dos.h b/imap/src/osdep/dos/tcp_dos.h
new file mode 100644
index 00000000..2933a5ff
--- /dev/null
+++ b/imap/src/osdep/dos/tcp_dos.h
@@ -0,0 +1,51 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: OS2 routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+/* TCP input buffer -- must be large enough to prevent overflow */
+
+#define BUFLEN 8192
+
+
+/* TCP I/O stream (must be before osdep.h is included) */
+
+#define TCPSTREAM struct tcp_stream
+TCPSTREAM {
+ char *host; /* host name */
+ unsigned long port; /* port number */
+ char *localhost; /* local host name */
+ int tcps; /* tcp socket */
+ long ictr; /* input counter */
+ char *iptr; /* input pointer */
+ char ibuf[BUFLEN]; /* input buffer */
+};
+
+
+/* Local function prototypes */
+
+long lookuphost (char **host,struct sockaddr_in *sin);
+long tcp_abort (TCPSTREAM *stream);
diff --git a/imap/src/osdep/dos/tcp_dwa.c b/imap/src/osdep/dos/tcp_dwa.c
new file mode 100644
index 00000000..4275f27c
--- /dev/null
+++ b/imap/src/osdep/dos/tcp_dwa.c
@@ -0,0 +1,344 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Waterloo DOS TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 13 January 2008
+ */
+
+
+/* Global data */
+
+short sock_initted = 0; /* global so others using net can see it */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd);
+
+/* TCP/IP manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tcp_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* TCP/IP open
+ * Accepts: host name
+ * contact service name
+ * contact port number
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *TCP_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = NIL;
+ tcp_Socket *sock;
+ char *s,tmp[MAILTMPLEN];
+ unsigned long adr,i,j,k,l;
+ port &= 0xffff; /* erase flags */
+ /* initialize if first time here */
+ if (!sock_initted++) sock_init();
+ /* The domain literal form is used (rather than simply the dotted decimal
+ as with other Unix programs) because it has to be a valid "host name"
+ in mailsystem terminology. */
+ /* look like domain literal? */
+ if (host[0] == '[' && host[strlen (host)-1] == ']') {
+ if (((i = strtoul (s = host+1,&s,10)) <= 255) && *s++ == '.' &&
+ ((j = strtoul (s,&s,10)) <= 255) && *s++ == '.' &&
+ ((k = strtoul (s,&s,10)) <= 255) && *s++ == '.' &&
+ ((l = strtoul (s,&s,10)) <= 255) && *s++ == ']' && !*s)
+ adr = (i << 24) + (j << 16) + (k << 8) + l;
+ else {
+ sprintf (tmp,"Bad format domain-literal: %.80s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ }
+ else { /* lookup host name */
+ if (!(adr = resolve (host))) {
+ sprintf (tmp,"Host not found: %s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ }
+
+ /* OK to instantiate socket now */
+ sock = (tcp_Socket *) fs_get (sizeof (tcp_Socket));
+ /* open connection */
+ if (!tcp_open (sock,(word) 0,adr,(word) port,NULL)) {
+ sprintf (tmp,"Can't connect to %.80s,%ld",host,port);
+ mm_log (tmp,ERROR);
+ fs_give ((void **) &sock);
+ return NIL;
+ }
+ /* create TCP/IP stream */
+ stream = (TCPSTREAM *) fs_get (sizeof (TCPSTREAM));
+ stream->host = cpystr (host); /* official host name */
+ stream->localhost = cpystr (mylocalhost ());
+ stream->port = port; /* port number */
+ stream->tcps = sock; /* init socket */
+ stream->ictr = 0; /* init input counter */
+ return stream; /* return success */
+}
+
+/* TCP/IP authenticated open
+ * Accepts: NETMBX specifier
+ * service name
+ * returned user name buffer
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL; /* always NIL on DOS */
+}
+
+/* TCP receive line
+ * Accepts: TCP stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *tcp_getline (TCPSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = tcp_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = tcp_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* TCP receive line or partial line
+ * Accepts: TCP stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!tcp_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!tcp_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* TCP/IP receive buffer
+ * Accepts: TCP/IP stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *buffer)
+{
+ unsigned long n;
+ char *bufptr = buffer;
+ while (size > 0) { /* until request satisfied */
+ if (!tcp_getdata (stream)) return NIL;
+ n = min (size,stream->ictr);/* number of bytes to transfer */
+ /* do the copy */
+ memcpy (bufptr,stream->iptr,(size_t) n);
+ bufptr += n; /* update pointer */
+ stream->iptr +=n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -=n;
+ }
+ bufptr[0] = '\0'; /* tie off string */
+ return T;
+}
+
+
+/* TCP/IP receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getdata (TCPSTREAM *stream)
+{
+ int status;
+ if (!stream->tcps) return NIL;/* no-no nuked socket */
+ while (stream->ictr < 1) { /* if buffer empty, block for input and read */
+ if (!_ip_delay1 (stream->tcps,600,NULL,&status))
+ stream->ictr = sock_fastread (stream->tcps,
+ stream->iptr = stream->ibuf,BUFLEN);
+ else if (status == 1) { /* nuke the socket if closed */
+ sock_close (stream->tcps);
+ fs_give ((void **) &stream->tcps);
+ return NIL;
+ }
+ }
+ return T;
+}
+
+/* TCP/IP send string as record
+ * Accepts: TCP/IP stream
+ * Returns: T if success else NIL
+ */
+
+long tcp_soutr (TCPSTREAM *stream,char *string)
+{
+ /* output the cruft */
+ sock_puts (stream->tcps,string);
+ return T; /* all done */
+}
+
+
+/* TCP/IP send string
+ * Accepts: TCP/IP stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
+{
+ sock_write (stream->tcps,string,(int) size);
+ return T;
+}
+
+
+/* TCP/IP close
+ * Accepts: TCP/IP stream
+ */
+
+void tcp_close (TCPSTREAM *stream)
+{
+ if (stream->tcps){ /* nuke the socket */
+ sock_close (stream->tcps);
+ _ip_delay2 (stream->tcps,0,NULL,NULL);
+ }
+ fs_give ((void **) &stream->tcps);
+ /* flush host names */
+ fs_give ((void **) &stream->host);
+ fs_give ((void **) &stream->localhost);
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+/* TCP/IP get host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_host (TCPSTREAM *stream)
+{
+ return stream->host; /* return host name */
+}
+
+
+/* TCP/IP get remote host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_remotehost (TCPSTREAM *stream)
+{
+ return stream->host; /* return host name */
+}
+
+
+/* TCP/IP return port for this stream
+ * Accepts: TCP/IP stream
+ * Returns: port number for this stream
+ */
+
+unsigned long tcp_port (TCPSTREAM *stream)
+{
+ return stream->port; /* return port number */
+}
+
+
+/* TCP/IP get local host name
+ * Accepts: TCP/IP stream
+ * Returns: local host name
+ */
+
+char *tcp_localhost (TCPSTREAM *stream)
+{
+ return stream->localhost; /* return local host name */
+}
+
+
+/* TCP/IP return canonical form of host name
+ * Accepts: host name
+ * Returns: canonical form of host name
+ */
+
+char *tcp_canonical (char *name)
+{
+ return name;
+}
+
+
+/* TCP/IP get client host name (server calls only)
+ * Returns: client host name
+ */
+
+char *tcp_clienthost ()
+{
+ return "UNKNOWN";
+}
diff --git a/imap/src/osdep/dos/tcp_dwa.h b/imap/src/osdep/dos/tcp_dwa.h
new file mode 100644
index 00000000..360eea85
--- /dev/null
+++ b/imap/src/osdep/dos/tcp_dwa.h
@@ -0,0 +1,45 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Waterloo DOS TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+/* TCP input buffer -- must be large enough to prevent overflow */
+
+#define BUFLEN 8192
+
+
+/* TCP I/O stream (must be before osdep.h is included) */
+
+#define TCPSTREAM struct tcp_stream
+TCPSTREAM {
+ char *host; /* host name */
+ unsigned long port; /* port number */
+ char *localhost; /* local host name */
+ tcp_Socket *tcps; /* tcp socket */
+ long ictr; /* input counter */
+ char *iptr; /* input pointer */
+ char ibuf[BUFLEN]; /* input buffer */
+};
diff --git a/imap/src/osdep/dos/tcp_wsk.c b/imap/src/osdep/dos/tcp_wsk.c
new file mode 100644
index 00000000..90e206a9
--- /dev/null
+++ b/imap/src/osdep/dos/tcp_wsk.c
@@ -0,0 +1,818 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Winsock TCP/IP routines
+ *
+ * Author: Mark Crispin from Mike Seibel's Winsock code
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 13 January 2008
+ */
+
+
+#define TCPMAXSEND 32768
+
+/* Private functions */
+
+int tcp_socket_open (struct sockaddr_in *sin,char *tmp,char *hst,
+ unsigned long port);
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd);
+long tcp_abort (TCPSTREAM *stream);
+long tcp_close_socket (SOCKET *sock);
+char *tcp_name (struct sockaddr_in *sin,long flag);
+char *tcp_name_valid (char *s);
+
+
+/* Private data */
+
+int wsa_initted = 0; /* init ? */
+static int wsa_sock_open = 0; /* keep track of open sockets */
+static tcptimeout_t tmoh = NIL; /* TCP timeout handler routine */
+static long ttmo_read = 0; /* TCP timeouts, in seconds */
+static long ttmo_write = 0;
+static long allowreversedns = T;/* allow reverse DNS lookup */
+static long tcpdebug = NIL; /* extra TCP debugging telemetry */
+
+/* TCP/IP manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tcp_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_TIMEOUT:
+ tmoh = (tcptimeout_t) value;
+ case GET_TIMEOUT:
+ ret = (void *) tmoh;
+ break;
+ case SET_READTIMEOUT:
+ ttmo_read = (long) value;
+ case GET_READTIMEOUT:
+ ret = (void *) ttmo_read;
+ break;
+ case SET_WRITETIMEOUT:
+ ttmo_write = (long) value;
+ case GET_WRITETIMEOUT:
+ ret = (void *) ttmo_write;
+ break;
+ case SET_ALLOWREVERSEDNS:
+ allowreversedns = (long) value;
+ case GET_ALLOWREVERSEDNS:
+ ret = (void *) allowreversedns;
+ break;
+ case SET_TCPDEBUG:
+ tcpdebug = (long) value;
+ case GET_TCPDEBUG:
+ ret = (void *) tcpdebug;
+ break;
+ }
+ return ret;
+}
+
+/* TCP/IP open
+ * Accepts: host name
+ * contact service name
+ * contact port number and optional silent flag
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = NIL;
+ int i;
+ SOCKET sock = INVALID_SOCKET;
+ int silent = (port & NET_SILENT) ? T : NIL;
+ char *s;
+ struct sockaddr_in sin;
+ struct hostent *he;
+ char hostname[MAILTMPLEN];
+ char tmp[MAILTMPLEN];
+ struct servent *sv = NIL;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (!wsa_initted++) { /* init Windows Sockets */
+ WSADATA wsock;
+ if (i = (int) WSAStartup (WSA_VERSION,&wsock)) {
+ wsa_initted = 0; /* in case we try again */
+ sprintf (tmp,"Unable to start Windows Sockets (%d)",i);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ }
+ port &= 0xffff; /* erase flags */
+ /* lookup service */
+ if (service && (sv = getservbyname (service,"tcp")))
+ port = ntohs (sin.sin_port = sv->s_port);
+ /* copy port number in network format */
+ else sin.sin_port = htons ((u_short) port);
+ /* The domain literal form is used (rather than simply the dotted decimal
+ as with other Windows programs) because it has to be a valid "host name"
+ in mailsystem terminology. */
+ sin.sin_family = AF_INET; /* family is always Internet */
+ /* look like domain literal? */
+ if (host[0] == '[' && host[(strlen (host))-1] == ']') {
+ strcpy (tmp,host+1); /* yes, copy number part */
+ tmp[strlen (tmp)-1] = '\0';
+ if ((sin.sin_addr.s_addr = inet_addr (tmp)) == INADDR_NONE) {
+ sprintf (tmp,"Bad format domain-literal: %.80s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ else {
+ sin.sin_family = AF_INET; /* family is always Internet */
+ strcpy (hostname,host);
+ (*bn) (BLOCK_TCPOPEN,NIL);
+ sock = tcp_socket_open (&sin,tmp,hostname,port);
+ (*bn) (BLOCK_NONE,NIL);
+ }
+ }
+
+ else { /* lookup host name */
+ if (tcpdebug) {
+ sprintf (tmp,"DNS resolution %.80s",host);
+ mm_log (tmp,TCPDEBUG);
+ }
+ (*bn) (BLOCK_DNSLOOKUP,NIL);/* look up name */
+ if (!(he = gethostbyname (lcase (strcpy (tmp,host)))))
+ sprintf (tmp,"Host not found (#%d): %s",WSAGetLastError(),host);
+ (*bn) (BLOCK_NONE,NIL);
+ if (he) { /* DNS resolution won? */
+ if (tcpdebug) mm_log ("DNS resolution done",TCPDEBUG);
+ /* copy address type */
+ sin.sin_family = he->h_addrtype;
+ /* copy host name */
+ strcpy (hostname,he->h_name);
+ wsa_sock_open++; /* prevent tcp_close_socket() from freeing in
+ loop */
+ for (i = 0; (sock == INVALID_SOCKET) && (s = he->h_addr_list[i]); i++) {
+ if (i && !silent) mm_log (tmp,WARN);
+ memcpy (&sin.sin_addr,s,he->h_length);
+ (*bn) (BLOCK_TCPOPEN,NIL);
+ sock = tcp_socket_open (&sin,tmp,hostname,port);
+ (*bn) (BLOCK_NONE,NIL);
+ }
+ wsa_sock_open--; /* undo protection */
+ }
+ }
+ if (sock == INVALID_SOCKET) { /* error? */
+ if (!silent) mm_log (tmp,ERROR);
+ tcp_close_socket (&sock); /* do possible cleanup action */
+ }
+ else { /* got a socket, create TCP/IP stream */
+ stream = (TCPSTREAM *) memset (fs_get (sizeof (TCPSTREAM)),0,
+ sizeof (TCPSTREAM));
+ stream->port = port; /* port number */
+ /* init socket */
+ stream->tcpsi = stream->tcpso = sock;
+ stream->ictr = 0; /* init input counter */
+ /* copy official host name */
+ stream->host = cpystr (hostname);
+ if (tcpdebug) mm_log ("Stream open and ready for read",TCPDEBUG);
+ }
+ return stream; /* return success */
+}
+
+/* Open a TCP socket
+ * Accepts: Internet socket address block
+ * scratch buffer
+ * host name for error message
+ * port number for error message
+ * Returns: socket if success, else -1 with error string in scratch buffer
+ */
+
+int tcp_socket_open (struct sockaddr_in *sin,char *tmp,char *hst,
+ unsigned long port)
+{
+ int sock;
+ char *s;
+ sprintf (tmp,"Trying IP address [%s]",inet_ntoa (sin->sin_addr));
+ mm_log (tmp,NIL);
+ /* get a TCP stream */
+ if ((sock = socket (sin->sin_family,SOCK_STREAM,0)) == INVALID_SOCKET) {
+ sprintf (tmp,"Unable to create TCP socket (%d)",WSAGetLastError());
+ return -1;
+ }
+ wsa_sock_open++; /* count this socket as open */
+ /* open connection */
+ if (connect (sock,(struct sockaddr *) sin,sizeof (struct sockaddr_in)) ==
+ SOCKET_ERROR) {
+ switch (WSAGetLastError ()){/* analyze error */
+ case WSAECONNREFUSED:
+ s = "Refused";
+ break;
+ case WSAENOBUFS:
+ s = "Insufficient system resources";
+ break;
+ case WSAETIMEDOUT:
+ s = "Timed out";
+ break;
+ case WSAEHOSTUNREACH:
+ s = "Host unreachable";
+ break;
+ default:
+ s = "Unknown error";
+ break;
+ }
+ sprintf (tmp,"Can't connect to %.80s,%ld: %s (%d)",hst,port,s,
+ WSAGetLastError ());
+ tcp_close_socket (&sock); /* flush socket */
+ sock = INVALID_SOCKET;
+ }
+ return sock; /* return the socket */
+}
+
+/* TCP/IP authenticated open
+ * Accepts: NETMBX specifier
+ * service name
+ * returned user name buffer
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL; /* always NIL on Windows */
+}
+
+/* TCP receive line
+ * Accepts: TCP stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *tcp_getline (TCPSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = tcp_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = tcp_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* TCP receive line or partial line
+ * Accepts: TCP stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!tcp_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!tcp_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* TCP/IP receive buffer
+ * Accepts: TCP/IP stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *s)
+{
+ unsigned long n;
+ /* make sure socket still alive */
+ if (stream->tcpsi == INVALID_SOCKET) return NIL;
+ /* can transfer bytes from buffer? */
+ if (n = min (size,stream->ictr)) {
+ memcpy (s,stream->iptr,n); /* yes, slurp as much as we can from it */
+ s += n; /* update pointer */
+ stream->iptr +=n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -=n;
+ }
+ if (size) {
+ int i;
+ fd_set fds;
+ struct timeval tmo;
+ time_t tc,t = time (0);
+ blocknotify_t bn=(blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ (*bn) (BLOCK_TCPREAD,NIL);
+ while (size > 0) { /* until request satisfied */
+ time_t tl = time (0);
+ if (tcpdebug) mm_log ("Reading TCP buffer",TCPDEBUG);
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_SET (stream->tcpsi,&fds);/* set bit in selection vector */
+ tmo.tv_sec = ttmo_read;
+ tmo.tv_usec = 0;
+ /* block and read */
+ switch ((stream->tcpsi == stream->tcpso) ?
+ select (stream->tcpsi+1,&fds,0,0,
+ ttmo_read ? &tmo : (struct timeval *) 0) : 1) {
+ case SOCKET_ERROR: /* error */
+ if (WSAGetLastError () != WSAEINTR) return tcp_abort (stream);
+ break;
+ case 0: /* timeout */
+ tc = time (0);
+ if (tmoh && ((*tmoh) (tc - t,tc - tl))) break;
+ return tcp_abort (stream);
+ default:
+ if (stream->tcpsi == stream->tcpso)
+ while (((i = recv (stream->tcpsi,s,(int) min (maxposint,size),0)) ==
+ SOCKET_ERROR) && (WSAGetLastError () == WSAEINTR));
+ else while (((i = read (stream->tcpsi,s,(int) min (maxposint,size))) <
+ 0) && (errno == EINTR));
+ switch (i) {
+ case SOCKET_ERROR: /* error */
+ case 0: /* no data read */
+ return tcp_abort (stream);
+ default:
+ s += i; /* point at new place to write */
+ size -= i; /* reduce byte count */
+ if (tcpdebug) mm_log ("Successfully read TCP buffer",TCPDEBUG);
+ }
+ }
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ }
+ *s = '\0'; /* tie off string */
+ return T;
+}
+
+/* TCP/IP receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getdata (TCPSTREAM *stream)
+{
+ struct timeval tmo;
+ int i;
+ fd_set fds;
+ time_t tc,t = time (0);
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ FD_ZERO (&fds); /* initialize selection vector */
+ if (stream->tcpsi == INVALID_SOCKET) return NIL;
+ (*bn) (BLOCK_TCPREAD,NIL);
+ tmo.tv_sec = ttmo_read;
+ tmo.tv_usec = 0;
+ while (stream->ictr < 1) { /* if nothing in the buffer */
+ time_t tl = time (0);
+ if (tcpdebug) mm_log ("Reading TCP data",TCPDEBUG);
+ FD_SET (stream->tcpsi,&fds);/* set bit in selection vector */
+ /* block and read */
+ switch ((stream->tcpsi == stream->tcpso) ?
+ select (stream->tcpsi+1,&fds,0,0,
+ ttmo_read ? &tmo : (struct timeval *) 0) : 1) {
+ case SOCKET_ERROR: /* error */
+ if (WSAGetLastError () != WSAEINTR) return tcp_abort (stream);
+ break;
+ case 0: /* timeout */
+ tc = time (0);
+ if (tmoh && ((*tmoh) (tc - t,tc - tl))) break;
+ return tcp_abort (stream);
+ default:
+ if (stream->tcpsi == stream->tcpso)
+ while (((i = recv (stream->tcpsi,stream->ibuf,BUFLEN,0)) ==
+ SOCKET_ERROR) && (WSAGetLastError () == WSAEINTR));
+ else while (((i = read (stream->tcpsi,stream->ibuf,BUFLEN)) < 0) &&
+ (errno == EINTR));
+ switch (i) {
+ case SOCKET_ERROR: /* error */
+ case 0: /* no data read */
+ return tcp_abort (stream);
+ default:
+ stream->ictr = i; /* set new byte count */
+ /* point at TCP buffer */
+ stream->iptr = stream->ibuf;
+ if (tcpdebug) mm_log ("Successfully read TCP data",TCPDEBUG);
+ }
+ }
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return T;
+}
+
+/* TCP/IP send string as record
+ * Accepts: TCP/IP stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long tcp_soutr (TCPSTREAM *stream,char *string)
+{
+ return tcp_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* TCP/IP send string
+ * Accepts: TCP/IP stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
+{
+ int i;
+ struct timeval tmo;
+ fd_set fds;
+ time_t tc,t = time (0);
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ tmo.tv_sec = ttmo_write;
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ if (stream->tcpso == INVALID_SOCKET) return NIL;
+ (*bn) (BLOCK_TCPWRITE,NIL);
+ while (size > 0) { /* until request satisfied */
+ time_t tl = time (0);
+ if (tcpdebug) mm_log ("Writing to TCP",TCPDEBUG);
+ FD_SET (stream->tcpso,&fds);/* set bit in selection vector */
+ /* block and write */
+ switch ((stream->tcpsi == stream->tcpso) ?
+ select (stream->tcpso+1,NULL,&fds,NULL,
+ tmo.tv_sec ? &tmo : (struct timeval *) 0) : 1) {
+ case SOCKET_ERROR: /* error */
+ if (WSAGetLastError () != WSAEINTR) return tcp_abort (stream);
+ break;
+ case 0: /* timeout */
+ tc = time (0);
+ if (tmoh && ((*tmoh) (tc - t,tc - tl))) break;
+ return tcp_abort (stream);
+ default:
+ if (stream->tcpsi == stream->tcpso)
+ while (((i = send (stream->tcpso,string,
+ (int) min (size,TCPMAXSEND),0)) == SOCKET_ERROR) &&
+ (WSAGetLastError () == WSAEINTR));
+ else while (((i = write (stream->tcpso,string,
+ min (size,TCPMAXSEND))) < 0) &&
+ (errno == EINTR));
+ if (i == SOCKET_ERROR) return tcp_abort (stream);
+ size -= i; /* count this size */
+ if (tcpdebug) mm_log ("successfully wrote to TCP",TCPDEBUG);
+ string += i;
+ }
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return T; /* all done */
+}
+
+
+/* TCP/IP close
+ * Accepts: TCP/IP stream
+ */
+
+void tcp_close (TCPSTREAM *stream)
+{
+ tcp_abort (stream); /* nuke the sockets */
+ /* flush host names */
+ if (stream->host) fs_give ((void **) &stream->host);
+ if (stream->remotehost) fs_give ((void **) &stream->remotehost);
+ if (stream->localhost) fs_give ((void **) &stream->localhost);
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* TCP/IP abort sockets
+ * Accepts: TCP/IP stream
+ * Returns: NIL, always
+ */
+
+long tcp_abort (TCPSTREAM *stream)
+{
+ if (stream->tcpsi != stream->tcpso) tcp_close_socket (&stream->tcpso);
+ else stream->tcpso = INVALID_SOCKET;
+ return tcp_close_socket (&stream->tcpsi);
+}
+
+
+/* TCP/IP abort stream
+ * Accepts: WinSock socket
+ * Returns: NIL, always
+ */
+
+long tcp_close_socket (SOCKET *sock)
+{
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* something to close? */
+ if (sock && (*sock != INVALID_SOCKET)) {
+ (*bn) (BLOCK_TCPCLOSE,NIL);
+ closesocket (*sock); /* WinSock socket close */
+ *sock = INVALID_SOCKET;
+ (*bn) (BLOCK_NONE,NIL);
+ wsa_sock_open--; /* drop this socket */
+ }
+ /* no more open streams? */
+ if (wsa_initted && !wsa_sock_open) {
+ mm_log ("Winsock cleanup",NIL);
+ wsa_initted = 0; /* no more sockets, so... */
+ WSACleanup (); /* free up resources until needed */
+ }
+ return NIL;
+}
+
+/* TCP/IP get host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_host (TCPSTREAM *stream)
+{
+ return stream->host; /* use tcp_remotehost() if want guarantees */
+}
+
+
+/* TCP/IP get remote host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_remotehost (TCPSTREAM *stream)
+{
+ if (!stream->remotehost) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ stream->remotehost = /* get socket's peer name */
+ ((getpeername (stream->tcpsi,(struct sockaddr *) &sin,&sinlen) ==
+ SOCKET_ERROR) || (sinlen <= 0)) ?
+ cpystr (stream->host) : tcp_name (&sin,NIL);
+ }
+ return stream->remotehost;
+}
+
+
+/* TCP/IP return port for this stream
+ * Accepts: TCP/IP stream
+ * Returns: port number for this stream
+ */
+
+unsigned long tcp_port (TCPSTREAM *stream)
+{
+ return stream->port; /* return port number */
+}
+
+
+/* TCP/IP get local host name
+ * Accepts: TCP/IP stream
+ * Returns: local host name
+ */
+
+char *tcp_localhost (TCPSTREAM *stream)
+{
+ if (!stream->localhost) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ stream->localhost = /* get socket's name */
+ ((stream->port & 0xffff000) ||
+ ((getsockname (stream->tcpsi,(struct sockaddr *) &sin,&sinlen) ==
+ SOCKET_ERROR) || (sinlen <= 0))) ?
+ cpystr (mylocalhost ()) : tcp_name (&sin,NIL);
+ }
+ return stream->localhost; /* return local host name */
+}
+
+/* TCP/IP get client host address (server calls only)
+ * Returns: client host address
+ */
+
+char *tcp_clientaddr ()
+{
+ if (!myClientAddr) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ myClientAddr = /* get stdin's peer name */
+ ((getpeername (0,(struct sockaddr *) &sin,&sinlen) == SOCKET_ERROR) ||
+ (sinlen <= 0)) ? cpystr ("UNKNOWN") : cpystr (inet_ntoa (sin.sin_addr));
+ }
+ return myClientAddr;
+}
+
+
+/* TCP/IP get client host name (server calls only)
+ * Returns: client host name
+ */
+
+char *tcp_clienthost ()
+{
+ if (!myClientHost) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ myClientHost = /* get stdin's peer name */
+ ((getpeername (0,(struct sockaddr *) &sin,&sinlen) == SOCKET_ERROR) ||
+ (sinlen <= 0)) ? cpystr ("UNKNOWN") : tcp_name (&sin,T);
+ }
+ return myClientHost;
+}
+
+/* TCP/IP get server host address (server calls only)
+ * Returns: server host address
+ */
+
+char *tcp_serveraddr ()
+{
+ if (!myServerAddr) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ myServerAddr = /* get stdin's peer name */
+ ((getsockname (0,(struct sockaddr *) &sin,&sinlen) == SOCKET_ERROR) ||
+ (sinlen <= 0)) ? cpystr ("UNKNOWN") : cpystr (inet_ntoa (sin.sin_addr));
+ }
+ return myServerAddr;
+}
+
+
+/* TCP/IP get server host name (server calls only)
+ * Returns: server host name
+ */
+
+static long myServerPort = -1;
+
+char *tcp_serverhost ()
+{
+ if (!myServerHost) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ if (!wsa_initted++) { /* init Windows Sockets */
+ WSADATA wsock;
+ if (WSAStartup (WSA_VERSION,&wsock)) {
+ wsa_initted = 0;
+ return "random-pc"; /* try again later? */
+ }
+ }
+ /* get stdin's name */
+ if ((getsockname (0,(struct sockaddr *) &sin,&sinlen) == SOCKET_ERROR) ||
+ (sinlen <= 0)) myServerHost = cpystr (mylocalhost ());
+ else {
+ myServerHost = tcp_name (&sin,NIL);
+ myServerPort = ntohs (sin.sin_port);
+ }
+ }
+ return myServerHost;
+}
+
+
+/* TCP/IP get server port number (server calls only)
+ * Returns: server port number
+ */
+
+long tcp_serverport ()
+{
+ if (!myServerHost) tcp_serverhost ();
+ return myServerPort;
+}
+
+/* TCP/IP return canonical form of host name
+ * Accepts: host name
+ * Returns: canonical form of host name
+ */
+
+char *tcp_canonical (char *name)
+{
+ char *ret,host[MAILTMPLEN];
+ struct hostent *he;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* look like domain literal? */
+ if (name[0] == '[' && name[strlen (name) - 1] == ']') return name;
+ (*bn) (BLOCK_DNSLOOKUP,NIL);
+ if (tcpdebug) {
+ sprintf (host,"DNS canonicalization %.80s",name);
+ mm_log (host,TCPDEBUG);
+ }
+ /* note that NT requires lowercase! */
+ ret = (he = gethostbyname (lcase (strcpy (host,name)))) ? he->h_name : name;
+ (*bn) (BLOCK_NONE,NIL);
+ if (tcpdebug) mm_log ("DNS canonicalization done",TCPDEBUG);
+ return ret;
+}
+
+
+/* TCP/IP return name from socket
+ * Accepts: socket
+ * verbose flag
+ * Returns: cpystr name
+ */
+
+char *tcp_name (struct sockaddr_in *sin,long flag)
+{
+ char *ret,*t,adr[MAILTMPLEN],tmp[MAILTMPLEN];
+ sprintf (ret = adr,"[%.80s]",inet_ntoa (sin->sin_addr));
+ if (allowreversedns) {
+ struct hostent *he;
+ blocknotify_t bn = (blocknotify_t)mail_parameters(NIL,GET_BLOCKNOTIFY,NIL);
+ void *data;
+ if (tcpdebug) {
+ sprintf (tmp,"Reverse DNS resolution %s",adr);
+ mm_log (tmp,TCPDEBUG);
+ }
+ (*bn) (BLOCK_DNSLOOKUP,NIL);/* quell alarms */
+ data = (*bn) (BLOCK_SENSITIVE,NIL);
+ /* translate address to name */
+ if (t = tcp_name_valid ((he = gethostbyaddr ((char *) &sin->sin_addr,
+ sizeof (struct in_addr),
+ sin->sin_family)) ?
+ (char *) he->h_name : NIL)) {
+ /* produce verbose form if needed */
+ if (flag) sprintf (ret = tmp,"%s %s",t,adr);
+ else ret = t;
+ }
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ (*bn) (BLOCK_NONE,NIL); /* alarms OK now */
+ if (tcpdebug) mm_log ("Reverse DNS resolution done",TCPDEBUG);
+ }
+ return cpystr (ret);
+}
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost (void)
+{
+ if (!myLocalHost) {
+ char tmp[MAILTMPLEN];
+ if (!wsa_initted++) { /* init Windows Sockets */
+ WSADATA wsock;
+ if (WSAStartup (WSA_VERSION,&wsock)) {
+ wsa_initted = 0;
+ return "random-pc"; /* try again later? */
+ }
+ }
+ myLocalHost = cpystr ((gethostname (tmp,MAILTMPLEN-1) == SOCKET_ERROR) ?
+ "random-pc" : tcp_canonical (tmp));
+ }
+ return myLocalHost;
+}
+
+
+/* Validate name
+ * Accepts: domain name
+ * Returns: T if valid, NIL otherwise
+ */
+
+char *tcp_name_valid (char *s)
+{
+ int c;
+ char *ret,*tail;
+ /* must be non-empty and not too long */
+ if ((ret = (s && *s) ? s : NIL) && (tail = ret + NETMAXHOST)) {
+ /* must be alnum, dot, or hyphen */
+ while ((c = *s++) && (s <= tail) &&
+ (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) ||
+ ((c >= '0') && (c <= '9')) || (c == '-') || (c == '.')));
+ if (c) ret = NIL;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/dos/tcp_wsk.h b/imap/src/osdep/dos/tcp_wsk.h
new file mode 100644
index 00000000..6e8f9af7
--- /dev/null
+++ b/imap/src/osdep/dos/tcp_wsk.h
@@ -0,0 +1,50 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Winsock TCP/IP routines
+ *
+ * Author: Mike Seibel from Unix version by Mark Crispin
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+/* TCP input buffer -- must be large enough to prevent overflow */
+
+#define BUFLEN 16384 /* 32768 causes stdin read() to barf */
+
+#include <windows.h>
+#define _INC_WINDOWS
+#include <winsock.h>
+
+
+/* TCP I/O stream (must be before osdep.h is included) */
+
+#define TCPSTREAM struct tcp_stream
+TCPSTREAM {
+ char *host; /* host name */
+ char *remotehost; /* remote host name */
+ unsigned long port; /* port number */
+ char *localhost; /* local host name */
+ SOCKET tcpsi; /* tcp socket */
+ SOCKET tcpso; /* tcp socket */
+ long ictr; /* input counter */
+ char *iptr; /* input pointer */
+ char ibuf[BUFLEN]; /* input buffer */
+};
diff --git a/imap/src/osdep/dos/write.c b/imap/src/osdep/dos/write.c
new file mode 100644
index 00000000..c7854815
--- /dev/null
+++ b/imap/src/osdep/dos/write.c
@@ -0,0 +1,59 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Write data, treating partial writes as an error
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 May 1995
+ * Last Edited: 30 August 2006
+ */
+
+/* The whole purpose of this unfortunate routine is to deal with DOS and
+ * certain cretinous versions of UNIX which decided that the "bytes actually
+ * written" return value from write() gave them license to use that for things
+ * that are really errors, such as disk quota exceeded, maximum file size
+ * exceeded, disk full, etc.
+ *
+ * BSD won't screw us this way on the local filesystem, but who knows what
+ * some NFS-mounted filesystem will do.
+ */
+
+#undef write
+
+/* Write data to file
+ * Accepts: file descriptor
+ * I/O vector structure
+ * number of vectors in structure
+ * Returns: number of bytes written if successful, -1 if failure
+ */
+
+long maxposint = (long)((((unsigned long) 1) << ((sizeof(int) * 8) - 1)) - 1);
+
+long safe_write (int fd,char *buf,long nbytes)
+{
+ long i,j;
+ if (nbytes > 0) for (i = nbytes; i; i -= j,buf += j) {
+ while (((j = write (fd,buf,(int) min (maxposint,i))) < 0) &&
+ (errno == EINTR));
+ if (j < 0) return j;
+ }
+ return nbytes;
+}
diff --git a/imap/src/osdep/mac/dummy.h b/imap/src/osdep/mac/dummy.h
new file mode 100644
index 00000000..32650e06
--- /dev/null
+++ b/imap/src/osdep/mac/dummy.h
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 9 May 1991
+ * Last Edited: 30 August 2006
+ */
+
+/* Exported function prototypes */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat);
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long scan_contents (DRIVER *dtb,char *name,char *contents,
+ unsigned long csiz,unsigned long fsiz);
+long dummy_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz);
+long dummy_create (MAILSTREAM *stream,char *mailbox);
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode);
+long dummy_delete (MAILSTREAM *stream,char *mailbox);
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname);
+char *dummy_file (char *dst,char *name);
+long dummy_canonicalize (char *tmp,char *ref,char *pat);
diff --git a/imap/src/osdep/mac/dummymac.c b/imap/src/osdep/mac/dummymac.c
new file mode 100644
index 00000000..a0d6d0d2
--- /dev/null
+++ b/imap/src/osdep/mac/dummymac.c
@@ -0,0 +1,295 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines for Mac
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 24 May 1993
+ * Last Edited: 30 August 2006
+ */
+
+
+#include <ctype.h>
+#include <stdio.h>
+#include "mail.h"
+#include "osdep.h"
+#include "dummy.h"
+#include "misc.h"
+
+/* Function prototypes */
+
+DRIVER *dummy_valid (char *name);
+void *dummy_parameters (long function,void *value);
+MAILSTREAM *dummy_open (MAILSTREAM *stream);
+void dummy_close (MAILSTREAM *stream,long options);
+long dummy_ping (MAILSTREAM *stream);
+void dummy_check (MAILSTREAM *stream);
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options);
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+/* Dummy routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER dummydriver = {
+ "dummy", /* driver name */
+ DR_LOCAL|DR_MAIL, /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ dummy_valid, /* mailbox is valid for us */
+ dummy_parameters, /* manipulate parameters */
+ dummy_scan, /* scan mailboxes */
+ dummy_list, /* list mailboxes */
+ dummy_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ dummy_delete, /* delete mailbox */
+ dummy_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ dummy_open, /* open mailbox */
+ dummy_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message structure */
+ NIL, /* fetch header */
+ NIL, /* fetch text */
+ NIL, /* fetch message data */
+ NIL, /* unique identifier */
+ NIL, /* message number from UID */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ dummy_ping, /* ping mailbox to see if still alive */
+ dummy_check, /* check for new messages */
+ dummy_expunge, /* expunge deleted messages */
+ dummy_copy, /* copy messages to another mailbox */
+ dummy_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+
+ /* prototype stream */
+MAILSTREAM dummyproto = {&dummydriver};
+
+/* Dummy validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *dummy_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ /* must be valid local mailbox */
+ return (name && *name && (*name != '{') && !compare_cstring (name,"INBOX")) ?
+ &dummydriver : NIL;
+}
+
+
+/* Dummy manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *dummy_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* Dummy scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ /* return silently */
+}
+
+
+/* Dummy list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ /* return silently */
+}
+
+
+/* Dummy list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ /* return silently */
+}
+
+/* Dummy create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * driver type to use
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create (MAILSTREAM *stream,char *mailbox)
+{
+ return NIL; /* always fails */
+}
+
+
+/* Dummy delete mailbox
+ * Accepts: mail stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return NIL; /* always fails */
+}
+
+
+/* Mail rename mailbox
+ * Accepts: mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ return NIL; /* always fails */
+}
+
+/* Dummy open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *dummy_open (MAILSTREAM *stream)
+{
+ char tmp[MAILTMPLEN];
+ /* OP_PROTOTYPE call or silence */
+ if (!stream || stream->silent) return NIL;
+ if (compare_cstring (stream->mailbox,"INBOX")) {
+ sprintf (tmp,"Not a mailbox: %s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ return NIL; /* always fails */
+ }
+ if (!stream->silent) { /* only if silence not requested */
+ mail_exists (stream,0); /* say there are 0 messages */
+ mail_recent (stream,0);
+ stream->uid_validity = time (0);
+ }
+ stream->inbox = T; /* note that it's an INBOX */
+ return stream; /* return success */
+}
+
+
+/* Dummy close
+ * Accepts: MAIL stream
+ * options
+ */
+
+void dummy_close (MAILSTREAM *stream,long options)
+{
+ /* return silently */
+}
+
+/* Dummy ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+long dummy_ping (MAILSTREAM *stream)
+{
+ return T;
+}
+
+
+/* Dummy check mailbox
+ * Accepts: MAIL stream
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+void dummy_check (MAILSTREAM *stream)
+{
+ dummy_ping (stream); /* invoke ping */
+}
+
+
+/* Dummy expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ return LONGT;
+}
+
+/* Dummy copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * options
+ * Returns: T if copy successful, else NIL
+ */
+
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) fatal ("Impossible dummy_copy");
+ return NIL;
+}
+
+
+/* Dummy append message string
+ * Accepts: mail stream
+ * destination mailbox
+ * append callback function
+ * data for callback
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Can't append to %s",mailbox);
+ mm_log (tmp,ERROR); /* pass up error */
+ return NIL; /* always fails */
+}
diff --git a/imap/src/osdep/mac/env_mac.c b/imap/src/osdep/mac/env_mac.c
new file mode 100644
index 00000000..d0a88df7
--- /dev/null
+++ b/imap/src/osdep/mac/env_mac.c
@@ -0,0 +1,236 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Mac environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 January 1992
+ * Last Edited: 30 August 2006
+ */
+
+
+static char *myHomeDir = NIL; /* home directory name */
+static char *myLocalHost = NIL; /* local host name */
+static char *myNewsrc = NIL; /* newsrc file name */
+
+#include "pmatch.c" /* include wildcard pattern matcher */
+
+/* Environment manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *env_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ char tmp[MAILTMPLEN];
+ switch ((int) function) {
+ case SET_HOMEDIR:
+ if (myHomeDir) fs_give ((void **) &myHomeDir);
+ myHomeDir = cpystr ((char *) value);
+ case GET_HOMEDIR:
+ /* set home directory if not defined */
+ if (!myHomeDir) myHomeDir = cpystr ("");
+ ret = (void *) myHomeDir;
+ break;
+ case SET_LOCALHOST:
+ myLocalHost = cpystr ((char *) value);
+ case GET_LOCALHOST:
+ ret = (void *) myLocalHost ? myLocalHost : "random-mac";
+ break;
+ case SET_NEWSRC:
+ if (myNewsrc) fs_give ((void **) &myNewsrc);
+ myNewsrc = cpystr ((char *) value);
+ case GET_NEWSRC:
+ if (!myNewsrc) { /* set news file name if not defined */
+ sprintf (tmp,"%s:News State",myhomedir ());
+ myNewsrc = cpystr (tmp);
+ }
+ ret = (void *) myNewsrc;
+ break;
+ }
+ return ret;
+}
+
+/* Write current time
+ * Accepts: destination string
+ * format of date and time
+ *
+ * This depends upon the ReadLocation() call in System 7 and the
+ * user properly setting his location/timezone in the Map control
+ * panel.
+ * Nothing is done about the gmtFlags.dlsDelta byte yet, since I
+ * don't know how it's supposed to work.
+ */
+
+static void do_date (char *date,char *fmt)
+{
+ long tz,tzm;
+ time_t ti = time (0);
+ struct tm *t = localtime (&ti);
+ MachineLocation loc;
+ ReadLocation (&loc); /* get location/timezone poop */
+ /* get sign-extended time zone */
+ tz = (loc.gmtFlags.gmtDelta & 0x00ffffff) |
+ ((loc.gmtFlags.gmtDelta & 0x00800000) ? 0xff000000 : 0);
+ tz /= 60; /* get timezone in minutes */
+ tzm = tz % 60; /* get minutes from the hour */
+ /* output time */
+ strftime (date,MAILTMPLEN,fmt,t);
+ /* now output time zone */
+ sprintf (date += strlen (date),"%+03ld%02ld",tz/60,tzm >= 0 ? tzm : -tzm);
+}
+
+
+/* Write current time in RFC 822 format
+ * Accepts: destination string
+ */
+
+void rfc822_date (char *date)
+{
+ do_date (date,"%a, %d %b %Y %H:%M:%S ");
+}
+
+
+/* Write current time in internal format
+ * Accepts: destination string
+ */
+
+void internal_date (char *date)
+{
+ do_date (date,"%2d-%b-%Y %H:%M:%S ");
+}
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost (void)
+{
+ return (char *) mail_parameters (NIL,GET_LOCALHOST,NIL);
+}
+
+
+/* Return my home directory name
+ * Returns: my home directory name
+ */
+
+char *myhomedir ()
+{
+ return (char *) mail_parameters (NIL,GET_HOMEDIR,NIL);
+}
+
+
+/* Determine default prototype stream to user
+ * Accepts: type (NIL for create, T for append)
+ * Returns: default prototype stream
+ */
+
+MAILSTREAM *default_proto (long type)
+{
+ extern MAILSTREAM dummyproto;
+ return &dummyproto; /* return default driver's prototype */
+}
+
+/* Block until event satisfied
+ * Called as: while (wait_condition && wait ());
+ * Returns T if OK, NIL if user wants to abort
+ *
+ * Allows user to run a desk accessory, select a different window, or go
+ * to another application while waiting for the event to finish. COMMAND/.
+ * will abort the wait.
+ * Assumes the Apple menu has the apple character as its first character,
+ * and that the main program has disabled all other menus.
+ */
+
+long wait ()
+{
+ EventRecord event;
+ WindowPtr window;
+ MenuInfo **m;
+ long r;
+ Str255 tmp;
+ /* wait for an event */
+ WaitNextEvent (everyEvent,&event,(long) 6,NIL);
+ switch (event.what) { /* got one -- what is it? */
+ case mouseDown: /* mouse clicked */
+ switch (FindWindow (event.where,&window)) {
+ case inMenuBar: /* menu bar item? */
+ /* yes, interesting event? */
+ if (r = MenuSelect (event.where)) {
+ /* round-about test for Apple menu */
+ if ((*(m = GetMHandle (HiWord (r))))->menuData[1] == appleMark) {
+ /* get desk accessory name */
+ GetItem (m,LoWord (r),tmp);
+ OpenDeskAcc (tmp); /* fire it up */
+ SetPort (window); /* put us back at our window */
+ }
+ else SysBeep (60); /* the fool forgot to disable it! */
+ }
+ HiliteMenu (0); /* unhighlight it */
+ break;
+ case inContent: /* some window was selected */
+ if (window != FrontWindow ()) SelectWindow (window);
+ break;
+ default: /* ignore all others */
+ break;
+ }
+ break;
+ case keyDown: /* key hit - if COMMAND/. then punt */
+ if ((event.modifiers & cmdKey) && (event.message & charCodeMask) == '.')
+ return NIL;
+ break;
+ default: /* ignore all others */
+ break;
+ }
+ return T; /* try wait test again */
+}
+
+/* Return random number
+ */
+
+long random ()
+{
+ return (long) rand () << 16 + rand ();
+}
+
+
+/* Emulator for BSD syslog() routine
+ * Accepts: priority
+ * message
+ * parameters
+ */
+
+void syslog (int priority,const char *message,...)
+{
+}
+
+
+/* Emulator for BSD openlog() routine
+ * Accepts: identity
+ * options
+ * facility
+ */
+
+void openlog (const char *ident,int logopt,int facility)
+{
+}
diff --git a/imap/src/osdep/mac/env_mac.h b/imap/src/osdep/mac/env_mac.h
new file mode 100644
index 00000000..4a5a8c97
--- /dev/null
+++ b/imap/src/osdep/mac/env_mac.h
@@ -0,0 +1,58 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Macintosh environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 25 May 1995
+ * Last Edited: 30 August 2006
+ */
+
+
+#define SUBSCRIPTIONFILE(t) sprintf (t,"%s:Mailbox List",myhomedir ())
+#define SUBSCRIPTIONTEMP(t) sprintf (t,"%s:Mailbox List Temp",myhomedir ())
+
+/* Function prototypes */
+
+#include "env.h"
+
+
+/* syslog() emulation */
+
+#define LOG_MAIL (2<<3) /* mail system */
+#define LOG_DAEMON (3<<3) /* system daemons */
+#define LOG_AUTH (4<<3) /* security/authorization messages */
+#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 signification condition */
+#define LOG_INFO 6 /* informational */
+#define LOG_DEBUG 7 /* debug-level messages */
+#define LOG_PID 0x01 /* log the pid with each message */
+#define LOG_CONS 0x02 /* log on the console if errors in sending */
+#define LOG_ODELAY 0x04 /* delay open until syslog() is called */
+#define LOG_NDELAY 0x08 /* don't delay open */
+#define LOG_NOWAIT 0x10 /* if forking to log on console, don't wait() */
+
+void openlog (const char *ident,int logopt,int facility);
+void syslog (int priority,const char *message,...);
diff --git a/imap/src/osdep/mac/fs_mac.c b/imap/src/osdep/mac/fs_mac.c
new file mode 100644
index 00000000..03908328
--- /dev/null
+++ b/imap/src/osdep/mac/fs_mac.c
@@ -0,0 +1,62 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Free storage management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Get a block of free storage
+ * Accepts: size of desired block
+ * Returns: free storage block
+ */
+
+void *fs_get (size_t size)
+{
+ void *block = malloc (size ? size : (size_t) 1);
+ if (!block) fatal ("Out of memory");
+ return (block);
+}
+
+
+/* Resize a block of free storage
+ * Accepts: ** pointer to current block
+ * new size
+ */
+
+void fs_resize (void **block,size_t size)
+{
+ if (!(*block = realloc (*block,size ? size : (size_t) 1)))
+ fatal ("Can't resize memory");
+}
+
+
+/* Return a block of free storage
+ * Accepts: ** pointer to free storage block
+ */
+
+void fs_give (void **block)
+{
+ free (*block);
+ *block = NIL;
+}
diff --git a/imap/src/osdep/mac/ftl_mac.c b/imap/src/osdep/mac/ftl_mac.c
new file mode 100644
index 00000000..782381b8
--- /dev/null
+++ b/imap/src/osdep/mac/ftl_mac.c
@@ -0,0 +1,39 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Mac crash management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 January 1992
+ * Last Edited: 30 August 2006
+ */
+
+/* Report a fatal error
+ * Accepts: string to output
+ */
+
+void fatal (char *string)
+{
+ mm_fatal (string); /* pass up the string */
+ /* nuke the resolver */
+ if (resolveropen) CloseResolver ();
+ abort (); /* die horribly */
+}
diff --git a/imap/src/osdep/mac/linkage.c b/imap/src/osdep/mac/linkage.c
new file mode 100644
index 00000000..14490a0c
--- /dev/null
+++ b/imap/src/osdep/mac/linkage.c
@@ -0,0 +1,37 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Default driver linkage
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 13 June 1995
+ * Last Edited: 23 May 2007
+ */
+
+ mail_link (&imapdriver); /* link in the imap driver */
+ mail_link (&nntpdriver); /* link in the nntp driver */
+ mail_link (&pop3driver); /* link in the pop3 driver */
+ mail_link (&dummydriver); /* link in the dummy driver */
+ auth_link (&auth_ext); /* link in the ext authenticator */
+ auth_link (&auth_md5); /* link in the md5 authenticator */
+ auth_link (&auth_pla); /* link in the plain authenticator */
+ auth_link (&auth_log); /* link in the log authenticator */
+ mail_versioncheck (CCLIENTVERSION); /* validate version */
diff --git a/imap/src/osdep/mac/linkage.h b/imap/src/osdep/mac/linkage.h
new file mode 100644
index 00000000..7e0eccfa
--- /dev/null
+++ b/imap/src/osdep/mac/linkage.h
@@ -0,0 +1,36 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Default driver linkage
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 13 June 1995
+ * Last Edited: 30 August 2006
+ */
+
+extern DRIVER imapdriver;
+extern DRIVER nntpdriver;
+extern DRIVER pop3driver;
+extern DRIVER dummydriver;
+extern AUTHENTICATOR auth_ext;
+extern AUTHENTICATOR auth_log;
+extern AUTHENTICATOR auth_md5;
+extern AUTHENTICATOR auth_pla;
diff --git a/imap/src/osdep/mac/mtest.sit.hqx b/imap/src/osdep/mac/mtest.sit.hqx
new file mode 100644
index 00000000..4089c44f
--- /dev/null
+++ b/imap/src/osdep/mac/mtest.sit.hqx
@@ -0,0 +1,171 @@
+(This file must be converted with BinHex 4.0)
+:#@edCA0d,R0TG!"6594%8dP8)3#3""l`!!!"4[4p8dP8)3!#!!!Hm(*-BA8#bJ#
+3!aB"#Jd!$'edCA0d,VNZFR0bB`#3%j@,!*!8"h-!N!6rN!4bFh*M8P0&4!%!TFk
+UMDPEIVJ!!"f3!!#3"JEY!*!%`hJ!N!KIY3c!q+l(4rK4`['9YjP(D"GY*FH1(-X
+cXLBR0(,Fbb2([#i[,dqCK"rKGS3@iEFb3JQeji4&VeZ[1Tp``PGTPS4GCi46F[`
+)*dr*J*-T2h+-c2N46VMfC-$*Vi46-Z(N'*P4`L5F(#G2#5GiGT&MK"*'9LD!erG
+CjLAcHUq9(@&*RN@fj0Ne))``F[c)XC9`B,`kD)!("P!!B+%+X!"Ij9MdF'6TDbk
+eDS@rkBHYZjA")jZhqKjc,5mCd(FTrHqIGj@qeG-@lI&rjBp%)kdK&fVILa2,2T2
+)Nrd4Q1G"rm8Y@pe%8-SkIQSGk2H@qIe3dIJcJrAH1R9c&i2J#HpTbZ[82H$e3hN
+Gr+r1$kAiHS*IpmchqmF6`GH$qi)l!%)YfjZ9XS-II05ZY0Hd[$N205VQN@SCP1m
+)Y64Y8k#VBqraRH@Q)09I"'0XA!G`I`Z`"YrDNrJL[SBi1UackpVJ'9h60qKDB,'
+ZI9HZ+l((G5@JIUk!US1Z-MDXaLiE+K4V$)T"`eF&6AH$4eI`"F3!(Y2Kj)ABCH6
+#b6&ffHN-mSr[[a($&e6dMmla&Iiep%rHVrQhHi#(0JaJe+#i-PJ&%!J"P1Cl!ep
+YQ2T$jSEJi2Z0c+hDh!RbcmB"'fT`%@-'kLB+!r6'c4USMFh0fb68Y(llUk)VPLr
+Ed,hR3+5c[D2(piA[kG@V9rTHlZTmZbd5lH`ji1X1qelC&HhSl'V[kHkL2X*kMAQ
+aAlcJMXYmY&+R(%SV"d9HaYKaMM2'05VCQ-Q6Z-8I6HNNK$jrP@'C#fhdRG$`*Iq
+%&@Cib6p[RSd[23pm09LaTDQaQHE,TKFhEZ)8(AZN&j&$@cB%&dm`1'D23KiA`i4
+k2MCcJkB$!`hclN6Fi$P,$&'1lZ1NTbALF5'11FV9NaR+!fJ,m`$QQX5KhSi!*8i
+0H"P%&KpL"M!d&Sm2'A1eSI*J(ND+Nh2H4CbM[(`bCJHJ,N8-+K58K!`YM1)PBC(
+MF0C+`KM%5qDK'9kBG#fF$!KI)Lr"'SQFp+A[c%U3!1a*+dC(4bhbb%J@6`TE[35
+MM(Nm(X4XR"M-a)90**KqZ"l(V4#iN!!lHpDXM)NM`EB,Cr%6K!Nq4h*KmA)pB*I
+)`FD3!b4&J$+3!2'`Cjcj!bJL9'E`T&l56UjPreMAX3FQ,GJZd#hK9K%451'Ll4J
+[Q!K6EaUrTfIlcI)Grdj,Q,L8F($6rf6,&2hjQq2Ql%alc$FQUj8Y-F8G'4R*miZ
+6r#N"Nhme6(qhkM0Gm4bIGkdBkR++CrL5,4D&DElPi!UIMbP,)&-ICUi63Q#+l`6
+Mr0435I-cJc8P-0hc+6l8CL,C!fPlFU@j0YHYP5HlVH6iFMqck@,jbDiKPVai*iE
+U$Ik%rBbr0$p6)-ka&c4LTeHf'r90kr2kc1PIL8)!1m$[-EHZbBe6E*MC$6#jJ*S
+D5ADDE#fUXfa6S4$,$R1GEqqVFra(qSj!BB$kcE6AAIeme%lY-+NSX$"!#a3%h`&
+Q*k"SP1XGG*8[q[)kriiqr[N`q606mMGrMr6Ym@A(IIpC!`re#`28larr"3,URqc
+[!JVpml$rlr*6*KrShqr[f3f'4hk$ZAhbP(CPb2rrCL3-m3pHa&&p!ZrP1,mSX("
+(RI3Qr550fhJiM2F2G!"KiYhmHX("5F#`F3Cii$"NqG''q!Q&BH'NM`)*!fm-NMJ
+G#iK#50`+6KKLT8)))B%,06GHI$PV%q'LL"-iAR[``!-0+5$5!(j#))h,X24N"1M
+Z3b$mh!-#ETlA5jqQAj-I-'p$,*iGIjfpb3C00LpePpdq'U&fJp!9LaP)F'!L&(#
+B-5QFhja-i,Ii3Z#f[f4jF[adr42@M*(aS%(r(cJ$V%XHH6)M*j%XNB0M090iPKm
+LA!c@+El%Er(aKNG'k+Uq'6Iie,4jIhRpArhcH[pGRlRkjh#kDmVcVp8l0@+3!*-
+C-3dda3h982@pIFIkIZcpjA`I1q0HjqjcpETeZPLFAMG*`Fd9K!Ihi0Vp`bA5LG*
+hUZq8VJ`VcUpGcBK0cMcR"h&$E,bKHRr0IYFEPf+ABZGi0Kklp*kV`G9iZ[%FBMH
+rb60J'$JdYQJ98C5$i*'hHLqX$cj("j*GmUV[S,cU1p,C'LAqPjfYZeGLrMhLP&p
+!I"@iP2Q)8riXB-@A8,d'p(lS*8`T3Z`S(+*+ZVBKKRHdK,Ph`N#[r#[[UN(HZi,
+RmL$f+@NM9SlBFD(YENEXX0#SAS2B+U&4(82X+D&4I3Da#U&48BGBJp#BAipB3'K
+82B2B3PQ$$a&l6QJ8DBJY%4T&Rb$Q&4UHaBLY&4UP5a&E)65mYBK9#BhL5X4@#Sf
+5"BMjK)Dk#,(5*lDrdp8@m8ADSYhl)UeYr`%0!!GYG'9cG#kj3)#D#J&2H#J!&c6
++!*!$J!#3"!&EEhJT,!#3%4B!N!MrN!438Np+5d&)6!%!UKPEcE$CcD)!!(*+!*!
+'&`d!N!3HUJ#3#1[c$J#9qCRFXlGVCEICCSIR+lY0&eVRAMZqXX[00PXS2r@Df1A
+KkHA*"VNBTjER*iZH[pFm&Z8ZE"qcfblm[',l@,%Y@BlrBZI+'dRENNhB`TG6`U+
+%rm+qcS9IXHh##19fLpl##EGFH-DfXHfjAGJq`Xl0#&Z"UN(Pee&qXHelQ@6(IQ(
+lb#E,Nrd4YNmb@CI$RfG![9r(Q-!B#jFc&X+DS8F3ArE,60Kl5)j0,',#3EXF'lA
+Ff"lcGF6XpqJ3VDZ[VCVRG&G6"[,RPeil*ApGbRl6q$rjPKb*9Y(1D'@k1jk56ib
+Eb(5E,A,-M0L$VA*XbY0-Yr-@1CBlLqRfhkE85@)CT[d855D6KeK',QTi*XDBC34
+L5e$cI81&5RrqMB2Q!HmIb'5XY(jK24d42+cfld3SK%*fYiD6QclP6#EAG(r)aeP
+A9eeCik`c3H*Pl-%"%f1(rd'SBd9bYr%[Ke#EaC*,Y$c-`"ZCB(aPl&BS5Mh,BLD
+N0[#0c'FQ[EfTSphSF9G+mm35REeC$@ABQb1@KQ[EQd)a(q9fL4i("NpNfN2KQ-m
+SrQ-S(2)pT,1(,a$YBE%N(&VZLmCm8CdpQL8R8qpEN@Q2GN5E4pEl1X,aD,-2+*'
+J,iSDk,LB&@mhVGUh%i%T`9"c@lc&*aE90$@hqTTDI0'15bkj3XE)'FC`$f"%"*l
+1P"DI2aMbLE&Sh#I+hk8AU)Im6@dGbUP#l9$8Yd)"kNG#BZUa&")[D50+5NM)&9b
+(+Q,e$K1L)[VIr25C($[MAIaX"HGhq)+1&j!!e[2jJj+Bi9hm,'b0jr&KUjKP@[a
+prP3lUX&jI')FUX(jqP([8`qIabF--RE1iir'PSMXGrLk$$B+fa(Rm6-0[2G([SX
+r"YZFmrJ'bKlZfl[ie([83qIV4le(06c22e6lh(IaUAI([SY2[8XIeB'afAb9Ir$
+Df*FU$RcS#1"L*RJ,Ea'm-fRG)RJrZ&A`AVj,m,SFJVIm#X%lcb9ikpb#ep-PH+p
+#E1P2"'r!,RL[ALpi`bm+hXK8V%Z`hSce%F(lBEhJM4F*hQXl"Hp(eQ,G*(MAbRj
+Q$[XD'T2@ZpNUa&HaZ`@Ri-$UI+HX[rmZrkFkaRE&f&5@+e`QI%6A4AYGekUNX",
+a$f$r%H%$&&q992!JbRG&K)XDRZXL%808*$F5I@jPT8B8j'B8aRlIb[FAeAq55dH
+1L1MQrZMBlqI#N6heG3RbIm0h"PIj6mS9SQrVVEIZf)'ii2IIHL[fZKNcr(lX)9e
+Qc0LaJqPIj*p+"@Tqh$jQE2+q8I`V51f,88$DMjNaBmC[idIcNIFrX['[3-eISDr
+,(liA2GB#PGG%M!Y$j0I99(,S4!lH0"+9j69jk$RX1"[J-k&q&K)M!KJPmk2bp!&
+6A(i)A$f%aJ9#mk**M)h2!k&-a+NY%$!d(f"'9ic!cqF$K"F86-V-6b31L6b5G`0
+1M&'($4C$r*(IEi'!bf%jZAp2)MJRAj%!m+V!I53X6@!NNPIi-M$VJ*EN'9#9N!"
+a&YKjY(!C*kkY+8YVm&1V[[Q*Em&G4"8%*T5f,[#88kD'H-`rfpl-l%cSlTfdA9f
+"JHN(J%eSm*4@)PpM4k`P',DM)A3i[eTGJ6&"`DPF8NCpe"&Fi@Z-%4"P0#'KVZJ
+[*D-5cj9JHF(B(,XfiL-JBI-6ph@T+dUX9+M'@9P0&@T[#VEaI(3iraee4Fp0i$M
+,UZ%S8Bqf"80A0`@dM*j99r5IR0'bfSC5UT%ah0(LLr"D$Z!BeBaU'aTVU*EKMXE
+fTQDHHdChlq40kSUZ9+[Z+8@0""1kSbfiP#2TZlpQQDfZk&!&D9'*Qh46eMA0V8e
+4Z8%STi[9&If[j942(BHFSX&33-Rq5%lCFLGG'%BMSCk#N4UTJi!'FlG!4h'FNPU
+T&$KMB#kf"'2"F+LT$GjI0$b-H3($G!YKeP3ZTYbbDi+G[TDDF![[Um`"V"&D(HU
+G[+-pdDB)cfQ`AL29cPmJ9B,h"%-m&1`FlT@F&1Fi2G6j(E%QQ8Nbd5%AUL[i8-(
+a90C32aYL`ACH(TD"2Rj'AF'05MqA53XEUIGmSH9U3qZ"%&GA'!MMC-i"%Nd8C3+
+*ea'RPkXVE!)&TEc"6VT5lqI&(N)CUk&iURNZrKLR,d,jQ,S#3d'4UZ9F3M,(%FV
+(eC9GU+*i5ZTi,V&QcP[%Ek[9&GfJ-QCP3`PRc'!(&4QFM[2Ve*@09rUKYEkmK,M
+I&28hchBiC+i'`RTe"FjV+U%9HSK`lP&ApKk`"H%%+aYUUF2-`Bj`iqcCPhd3LN,
+1kAje"C+FIA"HE5A2rZT`F(CM&%M03dM[I4dTVL&p3ehC*"A*8pR!1c`@l'LFj5M
+NkEqTJp$a'"p6ZV5kJEM'l'p6q*CA,Zf!&T,VPY)iT)9HdcLY3aT(IdlMC*l91)C
+c'SI8d"Q0SkQK8aT(8d1R0)ljV-E*rSh'XIa+iea`9Z-S@ZL8aY'dd%Q0SfQKNaT
+Re$Q0-rUdaY'8d!Q0Ne*#ac@1TS41D"a0#Ch31"HHd$LTbPMIi(A9bcQQ#EB&8Yl
+)!#XVHU$IbG$d`+Y1aS"Zd*h9$4RRG-22[C'MZL&GXcGbh(&BhDmZMP6LeqTL3!Z
+3!,Vi$jq8e-8C,D#ULhHdJ+BZ6QN"FK[He!*(0&$+EAM94dkhicBF8D$pEJ-T8*C
+5S1Q2e-9E@Z!"ZL1#F*R4@q-TDk#qP#q[J"K)kc#&J8b@U#Yh(T@-+R"h"VJc'!$
+*DASST808jr%G6d*c(NrT%10C(8*+i)`1k9F#*cd*c@NiT5kbckN,bePeFF&[e-@
+)AkN,43fF8KHD'MMT5@KUi+4U'(e@0B`jTaSd2A"#0D6d`((9S1Q"%kT"d`-R9)2
+e,GA`5krK4R90H3d004jd,$UX28B*bA-BIkkZQ-03F#4*aJQ&9*b"MU8C$*NYbQT
+ULE0UI1hKk,9DjLPPmpjMbNE12-AUkJ6'+kcqpJ6'YQ$+$aQS@ATZYc@P3c3rC%L
+(r,IEQY)KQJ*kABHNU[JH051TE"%j+UD3!1mDA*QLCE4'AE8j0#"j1,H'I,(f$TR
+G"QG*hRYLPQ63&Cjdc"@QUC)hAH(GfKcDXY)&0930FdZm[4fha2bA-j`f@JNpVDk
+D%P*jhFKjr98(HI`aII2fr1Uc#2q-&rQ-E(I[HlDS+e!8$X09@*c$fS,Y`4Ke#8I
+Ulf&00mJp,+Jp$*ErT@iB8#+N'hkQ4)5[k"mS9eIS"Xk+eVdecK+`M$!'9)Mf+JQ
+hYiG$(T9*G32-C$M0T,SlD`pZ89GYbZ3(5TiQ18m!03haSqNSRfY91+SPIXR0U5Q
+6YlNj@mf*lLqMR*b45*[2dp4fp@Yk3R(dDKX@8%@cX)m&fhK@`PHZkhT%AD%LE(+
+218Y,FEHFN!$ME'Q*qMSk&VFe8G2)+KhG1%eGY5Q6[@J4Z9lBZf5pqEU[A&V@!08
+R'%Tp(Ea83afESpCerS*+F*f3!$dr(QbqZLADG-fV26#+L30B)c8XMkq6#j!!`6i
+BVH*95KlUQC'9S9LEHPrD-(Z3!0I-mFS@P[(jKV,P[K"RaHK3GA09T0+bKF4K@D@
+qjF(AFaUVpN&jC6ARM2*JQi`c@0YaDNB0C498Uk`'Ak!kh06bDNBADVPA1MP5DE#
+T,4`BcXQU)LfUj'bBY5JBDJPI-maBip8Z"'m5BjM"Ql&SQ&1'EiLab(e4r'*T!9@
+daKH+$fG2p`$)c&DfQ1G%69S'0ZBj(I1JbqVVZ3GG&Sf'Sj688EHD2'LjRq4+'FY
+4+CRC"R$)JjElU84@k3h0d@#%FMqDd@5Y6V@eR&NpiA#EaTH$[5F1e65EDRS-kb+
+e!Z@9Lk('K+cbB'G08ka9eA[2U5Xi@0&Yi&,jaS,QL+Tr"cfA+FFm&rZ3!1FbpCc
+RmVkcRN[H1FrPMmjj,[RR2*GTjcbALpr`A!Ca,MRVG8ar`qXBp$$HIml$X*rf-!T
+1HaL&Tcf-5dpl')l6dfdc6Xc1lPCA0[2Rr8$cE!1GFjQ+9+i`Lepe,BiLcG+3!$c
+92#G8qdK1+IllJ)SN9FXjK63JM[4[kSUEPBjaVj*6bVhjS-U8cJ8HkL*M8cc@fN%
+ia*3TR$QUHk[JQ!QR%3VYpI'A)`l1pDRjPlVDZKR8AC&`C)CfadP+"j!!krZD$QJ
+HdJ%d!hK'"kLhMlbM!l6E4dlT!2*pcmcjTLD#hjRce5D#6bQ'IZIhT',)1Z[5QRr
+MdQUZc5qFS04%i0Y1N!!b$haUcPHE"cijjjY@jrD4JFN,ZRhNcCRmkp8e0Imb`1R
+UY%JkJ[QAe&4,rrc,bDQ@LHG[5,P[VHBk(kfEiMUREjK9'H!ZcA0qaF91KcHhR&j
+P9L@P4XJl2U0'IMkYm[EG*cHQ[)k"1HDd$kSK6Hq%@qTZ!XhV',UE)$h"V1raQ`6
+)D98bT,FH8-F&fjXL-k1A+[HTT'R3&cbMY,bZa!Te6EN54eRpIqj6qBA#+(a$B@b
+kmKXdUc'1pqCiTp4!rVUHliGR2&681hq`KVK@4[e"U962kp`5LYV[HFYMj6a!DSC
+ij9rP'9A'Ce5*@SlRqcTbPSVXk,m8rjAkMQrm6UUq-NFa2A%8CQ+2ZcSI5qQZS6X
+AdR2l1[pjBfBUCdee$GqiFP`hV$QL'`CQ9G)6h,apG+SfrI$0fkN*0-f[16@"4MF
+3r1m%fMVY[L5icNlFfm$-(AK4$hLChK'3!+EJh[idd'qB`$I%#2,M6%akN!"U#)p
+,qVqSJrqI(QlqArf8"brKC8[r3[T,rV)Hf6b-Jjj%IZ`a#hm3'K2Um0BX["Z+ap$
+2Zr*CM(8*&HaQi4RX+i@2#jm4RXC,X&B,EKjG,r`9R4"fim`DS3+ReJZI%'l$UGh
+#-cLcFIM-TiHaEdlPTV`c5hk9dfmr[*XU$F&(6`f,+$&b4N3QcX9E+,[hCEiSl`d
+kC6p1f6Z8IDZbAk[X0bRlEmVlV&*j6hbLj(GfRl83'pkeaclq0#PATlRUVaq4HPJ
+VeXlT4ELUF,(8Bj3UqT)2jUh(lZYjeq&NPp56FEI8Bc)JpM,@ql(QZ3,ZcQ65(@C
+kpb*h*a0XFj+ET*l-Uk5[@CRBcYUNQlVhimaY!401ZHQ8&*pr`(a9K%NpqJF$KUU
+E!VT%T43ACjXlr51Q&bfTPFUN(N1[D2%DmSV&5jL)r0ejA9*mHP'Tdq2%fE[&DPY
+RPNR8*qbSdM+F,mblERS4&(8$6Yq#q-5mp8#V,kY'I+eB,HS$&UP(pe8pNCl&[er
+U-mk8&LDIG"l)bV(94X5%25,X(,9N91p@CamE(FP-f"GX3%9ehAmTA5A&q4[r@%@
+Im5Gj@m`Va8jcjprbPfi'6$McI0iGE&c"E(1AAlGiJa4I[-(p[H5"+`VBE+Uaehc
+PKJ*6paI&bBRL3L1kjr&!@dAIiH[ceSSjJHGXa9@ImYX6da+9diY+H+dbR`j8%lC
+D-IDN9V%H`aT8F+@iiV!l%F56bSaqID)P-3eRRVH&fF`rA9G3b@C9ljkFp'bSQLf
+m2k$V6NVab-MZKaG[%0'EPMVTS(p%SBJDe!If*#i'ETIpXe)FrB-B'd&p`k-6dBP
+cM$Pi)*G$l04p+@,CHG2Q,ET0IK[U[MIa2V(1jVCC[@ER(QD+#GGCGfkIrh"brde
+VGklckk5iM5AXK#-+LDQfZYUANP[&@YFGE25P3I$4Z-4BejIG3@B4DlYIGPK4J4H
+R,RjXfdbVc4Uk)Q#VG`C'BmeEk-69h&p&K$&8a*'I41eeKakYef)lqQ1pMfj+(UD
+kk6BkTM&,94!c+k,(Y5bRhZA[M$4h2icp!F)$`Xfph@b%%VZ6BKCAmP"JDAXV-24
+dVZ6(9Yhpmrdl,%ecLr`2iT6"[)aGQ$cXZ'LadlELd-lH6ffXkXqpYIF6F[C+I+B
+D4bbImP2MkPl*Ear2l`ANPT%mj,MS3j4EV2G6[F$B@(@C-hPijVh5Kj%91N4iTZc
+(9[EBYfGD!`jdbG6!L(UR2eI19pMMY5U4(Zm)*E,G#fi@RNA''p'0P-(M9$&aa8m
+2Lh0qHVMU$V"@[L1Ij3GX8r1Cp8TAb3pCcU0lQ2ka+af(jKDjaSrH`bjh(1VH0pr
+ThiXH,qEm8qarN!$Rd)-FHU,*'10j6,JrRehZcjf&E0`2"#bl2REA(8c[dp[biF+
+QN!"h!IF[a,T)cXDZjpF9ER[q4ZEUBSAEN8pGP$QXK9CGmDVNppDK0m5CSlbMEG1
+BIZ0DacDUKQ-lVm4$bEhH@@*1BS&BI"64%T31-Ph!1K1Sk)&4lNJ1)e6TS'dhF2H
+*FjKC,2EVQJ`5[E#4&4BPl[YmZFe8@(6(ClpFIYIDJ!&m2pdV`R[3#`8qLYUmSa(
+9+9'V9irGT*+qj(EE@LQHZ%q+lcq!$Pr"G+DY,%2k-`E@!mFAPG5@PXePh3QaX3k
+m,jUNH2k+[SffkNr[qZlU*lBpZS[(9f`ra)TY-ke@a0EdhXc2q*8cTSdpIF[i#Bp
+b)UHhTmr$6lL8%hS$[9BQKaJQq6Q0lp$ab5eH2AD2%qZJXaSGeTR@cBXA2CcXUh)
+&$N[la%2+Q9[kcr3TCe`Kmdh1GX21BZFqTL[-SE13!+i&6&rJC(TE!6-90%,EIDm
+)0Q1Hi(CprB&mGV(J,R[S'lY+h5kADqZfR@ZUGl-Lff4NrJVUT9"jl$-i[rkYmbY
+&i56#p*2R,DN-UVU@6DaD[Fa5fP@k'LG&l1+P8+jX($m4aeNc2fR!UEJG,mj-*Zr
+UDMEB)0fPq1BEPPUkYVQ,(Fa9l#k@iRIGX(5#RFISP,[L#ZDZq15f3U1l`Q'8iY3
+4ckjpGKe3$R4YfhU$)f2l9RZ'Zq+4VHk+hKYmTSeVPZSTjiCLlh-ICD3)`4@'Z8E
+`a'451AR&L'GhIc&[XTLGRe&Im9*&IF8FShIF!-VDqJV$GAMQhq%&'`TBpjF+*d2
+pCIUI)Tljjl,E+[`6aBa%-E,iR$J'D%X3fCMI)-j,6)X)k"IplC0%%mkjmYdDaLa
+aRTMKY3jNDDZ[%)Y&G-1U``8QU,cU*lBQl0fIRrqYj*1hIMf[QZFbVkS-UR-+H""
+pjLMUhPI5Gc!(RE3CXCF4ZaHa,b$f)m4Z41`@a2iGX5rdDqBF-Cq0#jLaQaM)PIT
+B$$J4i1`(6Jca&X5p5Mb+Z!I9#+2A-4H!Phl1BFZK6)X'P1Pd)2UT'YLl+([Xje"
+&X(G3"E#rQ$,#hLBf5[&#iIEbUHk)jIC*K8+LS[#3!(52q$"f"m9[bQGYVFb8Q&`
+SL*@*#SPH*-BrdXA*r3'p@1bk+AAU$prJ+hU&X5pX[UJ04bEU2XP'ZCd0j"eZ*#m
+%qlqCaqq1CTX@9GH5ZhFh`M30XVe"RY0mK03(jKUH*(m%QFdK*`6lFR*!X+p[Z,+
+'iPH95189,&0B*NpR#(H9ZJKIZ,r848kam%eb)A$qm9,A!Z$S*TAMdQVBIhjH,+V
+Cf"I`*%GkTjmP&c&i6"6,f)mBhYe(XFaka1!98Xc`AF4!9K6,rL&HEiKhr9'XaBT
+c5)ZK9LdVF@iPjNB`#p&b(@+GF!U3!(2,HX3LQ2j!R9YZ4Lb'qBkPL(d'X6E8'Vh
+5mJA%FKKHG*M,@Zj%E)HFAmL!'0L'amb)i9Q$2$B#X30+E$4Lqr*I3PE--!@a2EB
+rTZG''UC4RA1R`,YMKKUFHkCM*Hi9CKB"XDFQ2B@EFTRK`iKpQePi[Dp(,5Nb&l&
+1a(*B&krhpi'a5ijPASAB6UEMHBp$V"$+Rf)6%)0DjV&*r!@-Q6aQ3mc%$$b@KaM
+F(Kkl'$',NSGG',YRVj+(!qHQ+RR-3LaAb@-1BK192+j!6&6bF!(lN!"F'`YHeEK
+RIfEle8fYEB2[A-fZ#erMLpD9d*0B"pl(UMkP0I8+9U()@"I&&&Md@Mh0K'A94F1
+aF$0Z,XqLDlNaF1$Vb#S4Ui0,Si#Ji5H4$`Vm2`!5FJ!!!3#3!`%8!*!$&!#3!c)
+U9*!(+LVX!!!U+P53#Z`!!#T8N!-*EA4PFh3ZFfPd!J#3!e0*9%46593K!3!!T!)
+-!*!$!3!!8dP84&0*9#%"!!#N!J`!N"+`fEpN!!!Hm!!!!8C892q3"953!q`!N!4
+8N!Er9*!&l!!!+P53"Iq3!e53!q`!N!-U9*!+l1`!N!-Ul&53"qcX!*!&l!$X9*!
+$l*!$!*!+l*!$!*!,!3#3!rq3#J#3#"!!+!!@!48#!3#3"J-!N!-"!*!$!43!N!-
+8!*!$-J&20H`KbJ#3!a`!-J!!8f9dC`#3!`S!!2rr!*!%!8pi(2'-:
diff --git a/imap/src/osdep/mac/nl_mac.c b/imap/src/osdep/mac/nl_mac.c
new file mode 100644
index 00000000..6c42cd08
--- /dev/null
+++ b/imap/src/osdep/mac/nl_mac.c
@@ -0,0 +1,74 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Mac newline routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 January 1992
+ * Last Edited: 30 August 2006
+ */
+
+/* Copy string with CRLF newlines
+ * Accepts: destination string
+ * pointer to size of destination string buffer
+ * source string
+ * length of source string
+ * Returns: length of copied string
+ */
+
+unsigned long strcrlfcpy (unsigned char **dst,unsigned long *dstl,
+ unsigned char *src,unsigned long srcl)
+{
+ long i,j;
+ unsigned char c,*d = src;
+ if (*dst) { /* destination provided? */
+ if ((i = srcl * 2) > *dstl) /* calculate worst-case situation */
+ for (i = j = srcl; j; --j) if (*d++ == '\015') i++;
+ /* flush destination buffer if too small */
+ if (i > *dstl) fs_give ((void **) dst);
+ }
+ /* make a new buffer if needed */
+ if (!*dst) *dst = (char *) fs_get ((*dstl = i) + 1);
+ d = *dst; /* destination string */
+ if (srcl) do { /* copy string */
+ c = *d++ = *src++; /* copy character */
+ /* append line feed to bare CR */
+ if ((c == '\015') && (*src != '\012')) *d++ = '\012';
+ } while (--srcl);
+ *d = '\0'; /* tie off destination */
+ return d - *dst; /* return length */
+}
+
+
+/* Length of string after strcrlfcpy applied
+ * Accepts: source string
+ * Returns: length of string
+ */
+
+unsigned long strcrlflen (STRING *s)
+{
+ unsigned long pos = GETPOS (s);
+ unsigned long i = SIZE (s);
+ unsigned long j = i;
+ while (j--) if ((SNX (s) == '\015') && ((CHR (s) != '\012') || !j)) i++;
+ SETPOS (s,pos); /* restore old position */
+ return i;
+}
diff --git a/imap/src/osdep/mac/os_mac.c b/imap/src/osdep/mac/os_mac.c
new file mode 100644
index 00000000..156eb5c3
--- /dev/null
+++ b/imap/src/osdep/mac/os_mac.c
@@ -0,0 +1,82 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Macintosh version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 January 1992
+ * Last Edited: 30 August 2006
+ */
+
+
+/* This is a totally new operating-system dependent module for the Macintosh,
+ * written using THINK C on my Mac PowerBook-100 in my free time.
+ * Unlike earlier efforts, this version requires no external TCP library. It
+ * also takes advantage of the Map panel in System 7 for the timezone.
+ */
+
+/* PPC cretins broke the MachineLocation struct */
+
+#define gmtFlags u
+
+#include <limits.h>
+#include <time.h>
+#include <stdio.h>
+#include <fcntl.h>
+#define tcp_port MacTCP_port
+#include <MacTCPCommonTypes.h>
+#include <AddressXlation.h>
+#include <TCPPB.h>
+#include <Desk.h>
+#include <Devices.h>
+#include <Errors.h>
+#include <Files.h>
+#include <Fonts.h>
+#include <Menus.h>
+#include <Script.h>
+#include <ToolUtils.h>
+#include <Windows.h>
+#undef tcp_port
+
+#include "tcp_mac.h" /* must be before osdep.h */
+#include "mail.h"
+#include "osdep.h"
+#include "misc.h"
+
+static short TCPdriver = 0; /* MacTCP's reference number */
+short resolveropen = 0; /* TCP's resolver open */
+
+
+#include "env_mac.c"
+#include "fs_mac.c"
+#include "ftl_mac.c"
+#include "nl_mac.c"
+#include "tcp_mac.c"
+
+#define open(a,b,c) open (a,b)
+#define server_login(user,pass,authuser,argc,argv) NIL
+#define authserver_login(user,authuser,argc,argv) NIL
+#define myusername() "" /* dummy definition to prevent build errors */
+#define MD5ENABLE ""
+
+#include "auth_md5.c"
+#include "auth_pla.c"
+#include "auth_log.c"
diff --git a/imap/src/osdep/mac/os_mac.h b/imap/src/osdep/mac/os_mac.h
new file mode 100644
index 00000000..5cdad642
--- /dev/null
+++ b/imap/src/osdep/mac/os_mac.h
@@ -0,0 +1,69 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Macintosh version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 January 1992
+ * Last Edited: 30 August 2006
+ */
+
+
+/* This is a totally new operating-system dependent module for the Macintosh,
+ * written using THINK C on my Mac PowerBook-100 in my free time.
+ * Unlike earlier efforts, this version requires no external TCP library. It
+ * also takes advantage of the Map panel in System 7 for the timezone.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <types.h>
+#include <unix.h>
+#ifndef noErr
+#include <Desk.h>
+#include <Devices.h>
+#include <Errors.h>
+#include <Events.h>
+#include <Fonts.h>
+#include <Memory.h>
+#include <Menus.h>
+#include <ToolUtils.h>
+#include <Windows.h>
+#endif
+
+#define L_SET SEEK_SET
+#define L_INCR SEEK_CUR
+#define L_XTND SEEK_END
+
+extern short resolveropen; /* make this global so caller can sniff */
+
+#include "env_mac.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+
+#define TCPDRIVER "\p.IPP"
+
+#define gethostid clock
+
+long wait (void);
+long random (void);
diff --git a/imap/src/osdep/mac/osdep.h b/imap/src/osdep/mac/osdep.h
new file mode 100644
index 00000000..9755c565
--- /dev/null
+++ b/imap/src/osdep/mac/osdep.h
@@ -0,0 +1,29 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Macintosh version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 13 June 1995
+ * Last Edited: 30 August 2006
+ */
+
+#include "os_mac.h"
diff --git a/imap/src/osdep/mac/pmatch.c b/imap/src/osdep/mac/pmatch.c
new file mode 100644
index 00000000..95a0bb86
--- /dev/null
+++ b/imap/src/osdep/mac/pmatch.c
@@ -0,0 +1,89 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: IMAP Wildcard Matching Routines (case-independent)
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 June 2000
+ * Last Edited: 30 August 2006
+ */
+
+/* Wildcard pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if pattern matches base, else NIL
+ */
+
+long pmatch_full (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ /* % at end, OK if no inferiors */
+ if (!pat[1]) return (delim && strchr (s,delim)) ? NIL : T;
+ /* scan remainder of string until delimiter */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while ((*s != delim) && *s++);
+ break;
+ case '*': /* match 0 or more characters */
+ if (!pat[1]) return T; /* * at end, unconditional match */
+ /* scan remainder of string */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while (*s++);
+ break;
+ case '\0': /* end of pattern */
+ return *s ? NIL : T; /* success if also end of base */
+ default: /* match this character */
+ return compare_uchar (*pat,*s) ? NIL : pmatch_full (s+1,pat+1,delim);
+ }
+ return NIL;
+}
+
+/* Directory pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if base is a matching directory of pattern, else NIL
+ */
+
+long dmatch (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ if (!*s) return T; /* end of base means have a subset match */
+ if (!*++pat) return NIL; /* % at end, no inferiors permitted */
+ /* scan remainder of string until delimiter */
+ do if (dmatch (s,pat,delim)) return T;
+ while ((*s != delim) && *s++);
+ if (*s && !s[1]) return T; /* ends with delimiter, must be subset */
+ return dmatch (s,pat,delim);/* do new scan */
+ case '*': /* match 0 or more characters */
+ return T; /* unconditional match */
+ case '\0': /* end of pattern */
+ break;
+ default: /* match this character */
+ if (*s) return compare_uchar (*pat,*s) ? NIL : dmatch (s+1,pat+1,delim);
+ /* end of base, return if at delimiter */
+ else if (*pat == delim) return T;
+ break;
+ }
+ return NIL;
+}
diff --git a/imap/src/osdep/mac/tcp_mac.c b/imap/src/osdep/mac/tcp_mac.c
new file mode 100644
index 00000000..8d39d4f3
--- /dev/null
+++ b/imap/src/osdep/mac/tcp_mac.c
@@ -0,0 +1,557 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Macintosh TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 January 1992
+ * Last Edited: 13 January 2008
+ */
+
+
+/* This is a totally new operating-system dependent module for the Macintosh,
+ * written using THINK C on my Mac PowerBook-100 in my free time.
+ * Unlike earlier efforts, this version requires no external TCP library. It
+ * also takes advantage of the Map panel in System 7 for the timezone.
+ */
+
+static tcptimeout_t tmoh = NIL; /* TCP timeout handler routine */
+static long ttmo_open = 75; /* TCP timeouts, in seconds */
+static long ttmo_read = 0;
+static long ttmo_write = 0;
+static long ttmo_close = 0;
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd);
+
+/* TCP/IP manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tcp_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_TIMEOUT:
+ tmoh = (tcptimeout_t) value;
+ case GET_TIMEOUT:
+ ret = (void *) tmoh;
+ break;
+ case SET_OPENTIMEOUT:
+ ttmo_open = (long) value;
+ case GET_OPENTIMEOUT:
+ ret = (void *) ttmo_open;
+ break;
+ case SET_READTIMEOUT:
+ ttmo_read = (long) value;
+ case GET_READTIMEOUT:
+ ret = (void *) ttmo_read;
+ break;
+ case SET_WRITETIMEOUT:
+ ttmo_write = (long) value;
+ case GET_WRITETIMEOUT:
+ ret = (void *) ttmo_write;
+ break;
+ case SET_CLOSETIMEOUT:
+ ttmo_close = (long) value;
+ case GET_CLOSETIMEOUT:
+ ret = (void *) ttmo_close;
+ break;
+ }
+ return ret;
+}
+
+/* TCP/IP open
+ * Accepts: host name
+ * contact service name
+ * contact port number
+ * Returns: TCP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream;
+ struct hostInfo hst;
+ struct TCPCreatePB *createpb;
+ struct TCPOpenPB *openpb;
+ char *s;
+ unsigned long i,j,k,l;
+ char tmp[MAILTMPLEN];
+ port &= 0xffff; /* erase flags */
+ /* init MacTCP */
+ if (!TCPdriver && OpenDriver (TCPDRIVER,&TCPdriver)) {
+ mm_log ("Can't init MacTCP",ERROR);
+ return NIL;
+ }
+ if (!resolveropen && OpenResolver (NIL)) {
+ mm_log ("Can't init domain resolver",ERROR);
+ return NIL;
+ }
+ resolveropen = T; /* note resolver open now */
+ /* domain literal? */
+ if (host[0] == '[' && host[strlen (host)-1] == ']') {
+ if (((i = strtoul (s = host+1,&s,10)) <= 255) && *s++ == '.' &&
+ ((j = strtoul (s,&s,10)) <= 255) && *s++ == '.' &&
+ ((k = strtoul (s,&s,10)) <= 255) && *s++ == '.' &&
+ ((l = strtoul (s,&s,10)) <= 255) && *s++ == ']' && !*s) {
+ hst.addr[0] = (i << 24) + (j << 16) + (k << 8) + l;
+ hst.addr[1] = 0; /* only one address to try! */
+ sprintf (hst.cname,"[%ld.%ld.%ld.%ld]",i,j,k,l);
+ }
+ else {
+ sprintf (tmp,"Bad format domain-literal: %.80s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ }
+
+ else { /* look up host name */
+ if (!tcp_dns_upp) tcp_dns_upp = NewResultProc (tcp_dns_result);
+ if (StrToAddr (host,&hst,tcp_dns_upp,NIL)) {
+ while (hst.rtnCode == cacheFault && wait ());
+ /* kludge around MacTCP bug */
+ if (hst.rtnCode == outOfMemory) {
+ mm_log ("Re-initializing domain resolver",WARN);
+ CloseResolver (); /* bop it on the head and try again */
+ OpenResolver (NIL); /* note this will leak 12K */
+ StrToAddr (host,&hst,tcp_dns_upp,NIL);
+ while (hst.rtnCode == cacheFault && wait ());
+ }
+ if (hst.rtnCode) { /* still have error status? */
+ switch (hst.rtnCode) { /* analyze return */
+ case nameSyntaxErr:
+ s = "Syntax error in name";
+ break;
+ case noResultProc:
+ s = "No result procedure";
+ break;
+ case noNameServer:
+ s = "No name server found";
+ break;
+ case authNameErr:
+ s = "Host does not exist";
+ break;
+ case noAnsErr:
+ s = "No name servers responding";
+ break;
+ case dnrErr:
+ s = "Name server returned an error";
+ break;
+ case outOfMemory:
+ s = "Not enough memory to resolve name";
+ break;
+ case notOpenErr:
+ s = "Driver not open";
+ break;
+ default:
+ s = NIL;
+ break;
+ }
+ if (s) sprintf (tmp,"%s: %.80s",s,host);
+ else sprintf (tmp,"Unknown resolver error (%ld): %.80s",
+ hst.rtnCode,host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ }
+ }
+
+ /* create local TCP/IP stream */
+ stream = (TCPSTREAM *) fs_get (sizeof (TCPSTREAM));
+ stream->ictr = 0; /* initialize input */
+ stream->pb.ioCRefNum = TCPdriver;
+ createpb = &stream->pb.csParam.create;
+ openpb = &stream->pb.csParam.open;
+ stream->pb.csCode = TCPCreate;/* create a TCP stream */
+ /* set up buffer for TCP */
+ createpb->rcvBuffLen = 4*BUFLEN;
+ createpb->rcvBuff = fs_get (createpb->rcvBuffLen);
+ createpb->notifyProc = NIL; /* no special notify procedure */
+ createpb->userDataPtr = NIL;
+ if (PBControlSync ((ParmBlkPtr) &stream->pb))
+ fatal ("Can't create TCP stream");
+ /* open TCP connection */
+ stream->pb.csCode = TCPActiveOpen;
+ openpb->ulpTimeoutValue = (int) ttmo_open;
+ openpb->ulpTimeoutAction = T;
+ openpb->validityFlags = timeoutValue|timeoutAction;
+ /* remote host (should try all) */
+ openpb->remoteHost = hst.addr[0];
+ openpb->remotePort = port; /* caller specified remote port */
+ openpb->localPort = 0; /* generate a local port */
+ openpb->tosFlags = 0; /* no special TOS */
+ openpb->precedence = 0; /* no special precedence */
+ openpb->dontFrag = 0; /* allow fragmentation */
+ openpb->timeToLive = 255; /* standards say 60, UNIX uses 255 */
+ openpb->security = 0; /* no special security */
+ openpb->optionCnt = 0; /* no IP options */
+ openpb->options[0] = 0;
+ openpb->userDataPtr = NIL; /* no special data pointer */
+ PBControlAsync ((ParmBlkPtr) &stream->pb);
+ while (stream->pb.ioResult == inProgress && wait ());
+ if (stream->pb.ioResult) { /* got back error status? */
+ sprintf (tmp,"Can't connect to %.80s,%ld",hst.cname,port);
+ mm_log (tmp,ERROR);
+ /* nuke the buffer */
+ stream->pb.csCode = TCPRelease;
+ createpb->userDataPtr = NIL;
+ if (PBControlSync ((ParmBlkPtr) &stream->pb)) fatal ("TCPRelease lossage");
+ /* free its buffer */
+ fs_give ((void **) &createpb->rcvBuff);
+ fs_give ((void **) &stream);/* and the local stream */
+ return NIL;
+ }
+
+ /* copy host names for later use */
+ stream->host = cpystr (hst.cname);
+ /* tie off trailing dot */
+ stream->host[strlen (stream->host) - 1] = '\0';
+ /* the open gave us our address */
+ i = (openpb->localHost >> 24) & 0xff;
+ j = (openpb->localHost >> 16) & 0xff;
+ k = (openpb->localHost >> 8) & 0xff;
+ l = openpb->localHost & 0xff;
+ sprintf (tmp,"[%ld.%ld.%ld.%ld]",i,j,k,l);
+ stream->localhost = cpystr (tmp);
+ if (!myLocalHost) myLocalHost = cpystr (tmp);
+ stream->port = port; /* copy port number */
+ return stream;
+}
+
+
+/* Called when have return from DNS
+ * Accepts: host info pointer
+ * user data pointer
+ */
+
+ResultUPP tcp_dns_upp = NIL;
+
+pascal void tcp_dns_result (struct hostInfo *hostInfoPtr,char *userDataPtr)
+{
+ /* dummy routine */
+}
+
+/* TCP/IP authenticated open
+ * Accepts: NETMBX specifier
+ * service name
+ * returned user name buffer
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL; /* no authenticated opens on Mac */
+}
+
+/* TCP receive line
+ * Accepts: TCP stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *tcp_getline (TCPSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = tcp_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = tcp_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* TCP receive line or partial line
+ * Accepts: TCP stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!tcp_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!tcp_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* TCP/IP receive buffer
+ * Accepts: TCP/IP stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *buffer)
+{
+ unsigned long n;
+ char *bufptr = buffer;
+ while (size > 0) { /* until request satisfied */
+ if (!tcp_getdata (stream)) return NIL;
+ n = min (size,stream->ictr);/* number of bytes to transfer */
+ /* do the copy */
+ memcpy (bufptr,stream->iptr,n);
+ bufptr += n; /* update pointer */
+ stream->iptr +=n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -=n;
+ }
+ bufptr[0] = '\0'; /* tie off string */
+ return T;
+}
+
+
+/* TCP/IP receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getdata (TCPSTREAM *stream)
+{
+ time_t t = time (0);
+ struct TCPReceivePB *receivepb = &stream->pb.csParam.receive;
+ struct TCPAbortPB *abortpb = &stream->pb.csParam.abort;
+ while (stream->ictr < 1) { /* if nothing in the buffer */
+ time_t tl = time (0);
+ stream->pb.csCode = TCPRcv; /* receive TCP data */
+ receivepb->commandTimeoutValue = (int) ttmo_read;
+ receivepb->rcvBuff = stream->ibuf;
+ receivepb->rcvBuffLen = BUFLEN;
+ receivepb->secondTimeStamp = 0;
+ receivepb->userDataPtr = NIL;
+ PBControlAsync ((ParmBlkPtr) &stream->pb);
+ while (stream->pb.ioResult == inProgress && wait ());
+ if (stream->pb.ioResult) { /* punt if got an error */
+ time_t tc = time (0);
+ if ((stream->pb.ioResult == commandTimeout) && tmoh &&
+ ((*tmoh) (tc - t,tc - tl, stream->host))) continue;
+ /* nuke connection */
+ stream->pb.csCode = TCPAbort;
+ abortpb->userDataPtr = NIL;
+ PBControlSync ((ParmBlkPtr) &stream->pb);
+ return NIL;
+ }
+ stream->iptr = stream->ibuf;/* point at TCP buffer */
+ stream->ictr = receivepb->rcvBuffLen;
+ }
+ return T;
+}
+
+/* TCP/IP send string as record
+ * Accepts: TCP/IP stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long tcp_soutr (TCPSTREAM *stream,char *string)
+{
+ return tcp_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* TCP/IP send string
+ * Accepts: TCP/IP stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
+{
+ struct TCPSendPB *sendpb = &stream->pb.csParam.send;
+ struct TCPAbortPB *abortpb = &stream->pb.csParam.abort;
+ struct {
+ unsigned short length;
+ Ptr buffer;
+ unsigned short trailer;
+ } wds;
+ while (wds.length = (size > (unsigned long) 32768) ? 32768 : size) {
+ wds.buffer = string; /* buffer */
+ wds.trailer = 0; /* tie off buffer */
+ size -= wds.length; /* this many words will be output */
+ string += wds.length;
+ stream->pb.csCode = TCPSend;/* send TCP data */
+ sendpb->ulpTimeoutValue = (int) ttmo_write;
+ sendpb->ulpTimeoutAction = 0;
+ sendpb->validityFlags = timeoutValue|timeoutAction;
+ sendpb->pushFlag = T; /* send the data now */
+ sendpb->urgentFlag = NIL; /* non-urgent data */
+ sendpb->wdsPtr = (Ptr) &wds;
+ sendpb->userDataPtr = NIL;
+ PBControlAsync ((ParmBlkPtr) &stream->pb);
+ while (stream->pb.ioResult == inProgress && wait ());
+ if (stream->pb.ioResult) { /* punt if got an error */
+ /* nuke connection */
+ stream->pb.csCode =TCPAbort;
+ abortpb->userDataPtr = NIL;
+ PBControlSync ((ParmBlkPtr) &stream->pb);
+ return NIL;
+ }
+ }
+ return T; /* success */
+}
+
+/* TCP/IP close
+ * Accepts: TCP/IP stream
+ */
+
+void tcp_close (TCPSTREAM *stream)
+{
+ struct TCPClosePB *closepb = &stream->pb.csParam.close;
+ struct TCPCreatePB *createpb = &stream->pb.csParam.create;
+ stream->pb.csCode = TCPClose; /* close TCP stream */
+ closepb->ulpTimeoutValue = (int) ttmo_close;
+ closepb->ulpTimeoutAction = 0;
+ closepb->validityFlags = timeoutValue|timeoutAction;
+ closepb->userDataPtr = NIL;
+ PBControlAsync ((ParmBlkPtr) &stream->pb);
+ while (stream->pb.ioResult == inProgress && wait ());
+ stream->pb.csCode =TCPRelease;/* flush the buffers */
+ createpb->userDataPtr = NIL;
+ if (PBControlSync ((ParmBlkPtr) &stream->pb)) fatal ("TCPRelease lossage");
+ /* free its buffer */
+ fs_give ((void **) &createpb->rcvBuff);
+ /* flush host names */
+ fs_give ((void **) &stream->host);
+ fs_give ((void **) &stream->localhost);
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+/* TCP/IP return host for this stream
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_host (TCPSTREAM *stream)
+{
+ return stream->host; /* return host name */
+}
+
+
+/* TCP/IP return remote host for this stream
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_remotehost (TCPSTREAM *stream)
+{
+ return stream->host; /* return host name */
+}
+
+
+/* TCP/IP return port for this stream
+ * Accepts: TCP/IP stream
+ * Returns: port number for this stream
+ */
+
+unsigned long tcp_port (TCPSTREAM *stream)
+{
+ return stream->port; /* return port number */
+}
+
+
+/* TCP/IP return local host for this stream
+ * Accepts: TCP/IP stream
+ * Returns: local host name for this stream
+ */
+
+char *tcp_localhost (TCPSTREAM *stream)
+{
+ return stream->localhost; /* return local host name */
+}
+
+/* TCP/IP return canonical form of host name
+ * Accepts: host name
+ * Returns: canonical form of host name
+ */
+
+char *tcp_canonical (char *name)
+{
+ int i;
+ struct hostInfo hst;
+ /* look like domain literal? */
+ if (name[0] == '[' && name[i = (strlen (name))-1] == ']') return name;
+ if (StrToAddr (name,&hst,tcp_dns_upp,NIL)) {
+ while (hst.rtnCode == cacheFault && wait ());
+ /* kludge around MacTCP bug */
+ if (hst.rtnCode == outOfMemory) {
+ mm_log ("Re-initializing domain resolver",WARN);
+ CloseResolver (); /* bop it on the head and try again */
+ OpenResolver (NIL); /* note this will leak 12K */
+ StrToAddr (name,&hst,tcp_dns_upp,NIL);
+ while (hst.rtnCode == cacheFault && wait ());
+ }
+ /* still have error status? */
+ if (hst.rtnCode) return name;
+ }
+ return hst.cname; /* success */
+}
+
+
+/* TCP/IP get client host name (server calls only)
+ * Returns: client host name
+ */
+
+char *tcp_clienthost ()
+{
+ return "UNKNOWN";
+}
diff --git a/imap/src/osdep/mac/tcp_mac.h b/imap/src/osdep/mac/tcp_mac.h
new file mode 100644
index 00000000..7e9b4a72
--- /dev/null
+++ b/imap/src/osdep/mac/tcp_mac.h
@@ -0,0 +1,49 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Macintosh TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 January 1992
+ * Last Edited: 30 August 2006
+ */
+
+
+/* TCP input buffer */
+
+#define BUFLEN (size_t) 8192 /* TCP input buffer */
+
+
+/* TCP I/O stream */
+
+#define TCPSTREAM struct tcp_stream
+TCPSTREAM {
+ char *host; /* host name */
+ unsigned long port; /* port number */
+ char *localhost; /* local host name */
+ struct TCPiopb pb; /* MacTCP parameter block */
+ long ictr; /* input counter */
+ char *iptr; /* input pointer */
+ char ibuf[BUFLEN]; /* input buffer */
+};
+
+extern ResultUPP tcp_dns_upp;
+pascal void tcp_dns_result (struct hostInfo *hostInfoPtr,char *userDataPtr);
diff --git a/imap/src/osdep/nt/drivers.bat b/imap/src/osdep/nt/drivers.bat
new file mode 100755
index 00000000..0964f537
--- /dev/null
+++ b/imap/src/osdep/nt/drivers.bat
@@ -0,0 +1,33 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Driver Linkage Generator for DOS/NT
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 11 October 1989
+REM Last Edited:30 August 2006
+
+REM Erase old driver linkage
+IF EXIST LINKAGE.* DEL LINKAGE.*
+
+REM Now define the new list
+FOR %%D IN (%1 %2 %3 %4 %5 %6 %7 %8 %9) DO CALL DRIVRAUX %%D
+
+EXIT 0
diff --git a/imap/src/osdep/nt/drivraux.bat b/imap/src/osdep/nt/drivraux.bat
new file mode 100755
index 00000000..30649a78
--- /dev/null
+++ b/imap/src/osdep/nt/drivraux.bat
@@ -0,0 +1,30 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Driver Linkage Generator auxillary for NT/Win9x
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 11 October 1989
+REM Last Edited:30 August 2006
+
+ECHO extern DRIVER %1driver; >> LINKAGE.H
+REM Note the introduction of the caret to quote the ampersand in NT
+if "%OS%" == "Windows_NT" ECHO mail_link (^&%1driver); /* link in the %1 driver */ >> LINKAGE.C
+if "%OS%" == "" ECHO mail_link (&%1driver); /* link in the %1 driver */ >> LINKAGE.C
diff --git a/imap/src/osdep/nt/dummy.h b/imap/src/osdep/nt/dummy.h
new file mode 100644
index 00000000..32650e06
--- /dev/null
+++ b/imap/src/osdep/nt/dummy.h
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 9 May 1991
+ * Last Edited: 30 August 2006
+ */
+
+/* Exported function prototypes */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat);
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long scan_contents (DRIVER *dtb,char *name,char *contents,
+ unsigned long csiz,unsigned long fsiz);
+long dummy_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz);
+long dummy_create (MAILSTREAM *stream,char *mailbox);
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode);
+long dummy_delete (MAILSTREAM *stream,char *mailbox);
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname);
+char *dummy_file (char *dst,char *name);
+long dummy_canonicalize (char *tmp,char *ref,char *pat);
diff --git a/imap/src/osdep/nt/dummynt.c b/imap/src/osdep/nt/dummynt.c
new file mode 100644
index 00000000..b44d271f
--- /dev/null
+++ b/imap/src/osdep/nt/dummynt.c
@@ -0,0 +1,724 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines for NT
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 24 May 1993
+ * Last Edited: 1 June 2007
+ */
+
+
+#include <ctype.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <direct.h>
+#include "mail.h"
+#include "osdep.h"
+#include <sys\stat.h>
+#include <dos.h>
+#include "dummy.h"
+#include "misc.h"
+
+/* Function prototypes */
+
+DRIVER *dummy_valid (char *name);
+void *dummy_parameters (long function,void *value);
+void dummy_list_work (MAILSTREAM *stream,char *dir,char *pat,char *contents,
+ long level);
+long dummy_listed (MAILSTREAM *stream,char delimiter,char *name,
+ long attributes,char *contents);
+long dummy_subscribe (MAILSTREAM *stream,char *mailbox);
+MAILSTREAM *dummy_open (MAILSTREAM *stream);
+void dummy_close (MAILSTREAM *stream,long options);
+long dummy_ping (MAILSTREAM *stream);
+void dummy_check (MAILSTREAM *stream);
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options);
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+/* Dummy routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER dummydriver = {
+ "dummy", /* driver name */
+ DR_LOCAL|DR_MAIL, /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ dummy_valid, /* mailbox is valid for us */
+ dummy_parameters, /* manipulate parameters */
+ dummy_scan, /* scan mailboxes */
+ dummy_list, /* list mailboxes */
+ dummy_lsub, /* list subscribed mailboxes */
+ dummy_subscribe, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ dummy_delete, /* delete mailbox */
+ dummy_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ dummy_open, /* open mailbox */
+ dummy_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message structure */
+ NIL, /* fetch header */
+ NIL, /* fetch text */
+ NIL, /* fetch message data */
+ NIL, /* unique identifier */
+ NIL, /* message number from UID */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ dummy_ping, /* ping mailbox to see if still alive */
+ dummy_check, /* check for new messages */
+ dummy_expunge, /* expunge deleted messages */
+ dummy_copy, /* copy messages to another mailbox */
+ dummy_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+
+ /* prototype stream */
+MAILSTREAM dummyproto = {&dummydriver};
+
+/* Dummy validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *dummy_valid (char *name)
+{
+ char *s,*t,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* must be valid local mailbox */
+ if (name && *name && (*name != '{') && (s = mailboxfile (tmp,name))) {
+ /* indeterminate INBOX */
+ if (!*s) return &dummydriver;
+ /* remove trailing \ */
+ if ((t = strrchr (s,'\\')) && !t[1]) *t = '\0';
+ if (!stat (s,&sbuf)) switch (sbuf.st_mode & S_IFMT) {
+ case S_IFREG: /* file */
+ case S_IFDIR: /* future use */
+ return &dummydriver;
+ }
+ }
+ return NIL;
+}
+
+
+/* Dummy manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *dummy_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* Dummy scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ char *s,test[MAILTMPLEN],file[MAILTMPLEN];
+ long i = 0;
+ if (!pat || !*pat) { /* empty pattern? */
+ if (dummy_canonicalize (test,ref,"*")) {
+ /* tie off name at root */
+ if (s = strchr (test,'\\')) *++s = '\0';
+ else test[0] = '\0';
+ dummy_listed (stream,'\\',test,LATT_NOSELECT,NIL);
+ }
+ }
+ /* get canonical form of name */
+ else if (dummy_canonicalize (test,ref,pat)) {
+ /* found any wildcards? */
+ if (s = strpbrk (test,"%*")) {
+ /* yes, copy name up to that point */
+ strncpy (file,test,(size_t) (i = s - test));
+ file[i] = '\0'; /* tie off */
+ }
+ else strcpy (file,test); /* use just that name then */
+ /* find directory name */
+ if (s = strrchr (file,'\\')) {
+ *++s = '\0'; /* found, tie off at that point */
+ s = file;
+ }
+ /* silly case */
+ else if (file[0] == '#') s = file;
+ /* do the work */
+ dummy_list_work (stream,s,test,contents,0);
+ if (pmatch ("INBOX",test)) /* always an INBOX */
+ dummy_listed (stream,NIL,"INBOX",LATT_NOINFERIORS,contents);
+ }
+}
+
+/* Dummy list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ dummy_scan (stream,ref,pat,NIL);
+}
+
+
+/* Dummy list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ void *sdb = NIL;
+ char *s,*t,test[MAILTMPLEN];
+ int showuppers = pat[strlen (pat) - 1] == '%';
+ /* get canonical form of name */
+ if (dummy_canonicalize (test,ref,pat) && (s = sm_read (&sdb))) do
+ if (*s != '{') {
+ if (pmatch_full (s,test,'\\')) {
+ if (pmatch (s,"INBOX")) mm_lsub (stream,NIL,s,LATT_NOINFERIORS);
+ else mm_lsub (stream,'\\',s,NIL);
+ }
+ else while (showuppers && (t = strrchr (s,'\\'))) {
+ *t = '\0'; /* tie off the name */
+ if (pmatch_full (s,test,'\\')) mm_lsub (stream,'\\',s,LATT_NOSELECT);
+ }
+ }
+ while (s = sm_read (&sdb)); /* until no more subscriptions */
+}
+
+
+/* Dummy subscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to add to subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_subscribe (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* must be valid local mailbox */
+ if ((s = mailboxfile (tmp,mailbox)) && *s && !stat (s,&sbuf) &&
+ ((sbuf.st_mode & S_IFMT) == S_IFREG)) return sm_subscribe (mailbox);
+ sprintf (tmp,"Can't subscribe %.80s: not a mailbox",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+}
+
+/* Dummy list mailboxes worker routine
+ * Accepts: mail stream
+ * directory name to search
+ * search pattern
+ * string to scan
+ * search level
+ */
+
+void dummy_list_work (MAILSTREAM *stream,char *dir,char *pat,char *contents,
+ long level)
+{
+ struct _finddata_t f;
+ struct stat sbuf;
+ long fhandle;
+ char tmp[MAILTMPLEN];
+ size_t len = 0;
+ /* punt if bogus name */
+ if (!mailboxdir (tmp,dir,NIL)) return;
+ /* make directory wildcard */
+ strcat (tmp,(tmp[strlen (tmp) -1] == '\\') ? "*.*" : "\\*.*");
+ /* do nothing if can't open directory */
+ if ((fhandle = _findfirst (tmp,&f)) >= 0) {
+ /* list it if at top-level */
+ if (!level && dir && pmatch_full (dir,pat,'\\'))
+ dummy_listed (stream,'\\',dir,LATT_NOSELECT,contents);
+ /* scan directory */
+ if (!dir || dir[(len = strlen (dir)) - 1] == '\\') do
+ if (((f.name[0] != '.') ||
+ (f.name[1] && ((f.name[1] != '.') || f.name[2]))) &&
+ ((len + strlen (f.name)) <= NETMAXMBX)) {
+ /* see if name is useful */
+ if (dir) sprintf (tmp,"%s%s",dir,f.name);
+ else strcpy (tmp,f.name);
+ /* make sure useful and can get info */
+ if ((pmatch_full (tmp,pat,'\\') ||
+ pmatch_full (strcat (tmp,"\\"),pat,'\\') ||
+ dmatch (tmp,pat,'\\')) &&
+ mailboxdir (tmp,dir,f.name) && tmp[0] && !stat (tmp,&sbuf)) {
+ /* now make name we'd return */
+ if (dir) sprintf (tmp,"%s%s",dir,f.name);
+ else strcpy (tmp,f.name);
+ /* only interested in file type */
+ switch (sbuf.st_mode & S_IFMT) {
+ case S_IFDIR: /* directory? */
+ if (pmatch_full (tmp,pat,'\\')) {
+ if (!dummy_listed (stream,'\\',tmp,LATT_NOSELECT,contents))break;
+ strcat (tmp,"\\");/* set up for dmatch call */
+ }
+ /* try again with trailing \ */
+ else if (pmatch_full (strcat (tmp,"\\"),pat,'\\') &&
+ !dummy_listed (stream,'\\',tmp,LATT_NOSELECT,contents))
+ break;
+ if (dmatch (tmp,pat,'\\') &&
+ (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL)))
+ dummy_list_work (stream,tmp,pat,contents,level+1);
+ break;
+ case S_IFREG: /* ordinary name */
+ if (pmatch_full (tmp,pat,'\\') && !pmatch ("INBOX",tmp))
+ dummy_listed (stream,'\\',tmp,LATT_NOINFERIORS,contents);
+ break;
+ }
+ }
+ }
+ while (!_findnext (fhandle,&f));
+ _findclose(fhandle);
+ }
+}
+
+/* Mailbox found
+ * Accepts: hierarchy delimiter
+ * mailbox name
+ * attributes
+ * contents to search before calling mm_list()
+ * Returns: T, always
+ */
+
+#define BUFSIZE 4*MAILTMPLEN
+
+long dummy_listed (MAILSTREAM *stream,char delimiter,char *name,
+ long attributes,char *contents)
+{
+ struct stat sbuf;
+ struct _finddata_t f;
+ int fd,nochild;
+ long fhandle,csiz,ssiz,bsiz;
+ char *s,*buf,tmp[MAILTMPLEN];
+ /* if not \NoInferiors */
+ if (!(attributes & LATT_NOINFERIORS) && mailboxdir (tmp,name,NIL) &&
+ strcat (tmp,(tmp[strlen (tmp) -1] == '\\') ? "*.*" : "\\*.*") &&
+ ((fhandle = _findfirst (tmp,&f)) >= 0)) {
+ nochild = T;
+ do if ((f.name[0] != '.') || (f.name[1] && ((f.name[1] != '.') ||
+ f.name[2]))) nochild = NIL;
+ while (nochild && !_findnext (fhandle,&f));
+ attributes |= nochild ? LATT_HASNOCHILDREN : LATT_HASCHILDREN;
+ _findclose (fhandle); /* all done, flush directory */
+ }
+ if (contents) { /* want to search contents? */
+ /* forget it if can't select or open */
+ if ((attributes & LATT_NOSELECT) || !(csiz = strlen (contents)) ||
+ !(s = dummy_file (tmp,name)) || stat (s,&sbuf) ||
+ (csiz > sbuf.st_size) || ((fd = open (tmp,O_RDONLY,NIL)) < 0))
+ return T;
+ /* get buffer including slop */
+ buf = (char *) fs_get (BUFSIZE + (ssiz = 4 * ((csiz / 4) + 1)) + 1);
+ memset (buf,'\0',ssiz); /* no slop area the first time */
+ while (sbuf.st_size) { /* until end of file */
+ read (fd,buf+ssiz,bsiz = min (sbuf.st_size,BUFSIZE));
+ if (search ((unsigned char *) buf,bsiz+ssiz,
+ (unsigned char *) contents,csiz)) break;
+ memcpy (buf,buf+BUFSIZE,ssiz);
+ sbuf.st_size -= bsiz; /* note that we read that much */
+ }
+ fs_give ((void **) &buf); /* flush buffer */
+ close (fd); /* finished with file */
+ if (!sbuf.st_size) return T;/* not found */
+ }
+ /* notify main program */
+ mm_list (stream,delimiter,name,attributes);
+ return T;
+}
+
+/* Dummy create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create (MAILSTREAM *stream,char *mailbox)
+{
+ char tmp[MAILTMPLEN];
+ if (compare_cstring (mailbox,"INBOX") && dummy_file (tmp,mailbox))
+ return dummy_create_path (stream,tmp,NIL);
+ sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+}
+
+
+/* Dummy create path
+ * Accepts: mail stream
+ * path name to create
+ * directory mode
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode)
+{
+ struct stat sbuf;
+ char c,*s,tmp[MAILTMPLEN];
+ int fd;
+ long ret = NIL;
+ char *t = strrchr (path,'\\');
+ char *pt = (path[1] == ':') ? path + 2 : path;
+ int wantdir = t && !t[1];
+ if (wantdir) *t = '\0'; /* flush trailing delimiter for directory */
+ /* found superior to this name? */
+ if ((s = strrchr (pt,'\\')) && (s != pt)) {
+ strncpy (tmp,path,(size_t) (s - path));
+ tmp[s - path] = '\0'; /* make directory name for stat */
+ c = *++s; /* tie off in case need to recurse */
+ *s = '\0';
+ /* name doesn't exist, create it */
+ if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,path,dirmode)) return NIL;
+ *s = c; /* restore full name */
+ }
+ if (wantdir) { /* want to create directory? */
+ ret = !mkdir (path);
+ *t = '\\'; /* restore directory delimiter */
+ }
+ /* create file */
+ else if ((fd = open (path,O_WRONLY|O_CREAT|O_EXCL,S_IREAD|S_IWRITE)) >= 0)
+ ret = !close (fd); /* close file */
+ if (!ret) { /* error? */
+ sprintf (tmp,"Can't create mailbox node %.80s: %.80s",path,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+ return ret; /* return status */
+}
+
+/* Dummy delete mailbox
+ * Accepts: mail stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_delete (MAILSTREAM *stream,char *mailbox)
+{
+ struct stat sbuf;
+ char *s,tmp[MAILTMPLEN];
+ if (!(s = dummy_file (tmp,mailbox))) {
+ sprintf (tmp,"Can't delete - invalid name: %.80s",s);
+ mm_log (tmp,ERROR);
+ }
+ /* no trailing \ */
+ if ((s = strrchr (tmp,'\\')) && !s[1]) *s = '\0';
+ if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) == S_IFDIR) ?
+ rmdir (tmp) : unlink (tmp)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %.80s",mailbox,strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ return T; /* return success */
+}
+
+
+/* Mail rename mailbox
+ * Accepts: mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ struct stat sbuf;
+ char c,*s,tmp[MAILTMPLEN],mbx[MAILTMPLEN],oldname[MAILTMPLEN];
+ long ret = NIL;
+ /* no trailing \ allowed */
+ if (!dummy_file (oldname,old) || !(s = dummy_file (mbx,newname)) ||
+ stat (oldname,&sbuf) || ((s = strrchr (s,'\\')) && !s[1] &&
+ ((sbuf.st_mode & S_IFMT) != S_IFDIR))) {
+ sprintf (mbx,"Can't rename %.80s to %.80s: invalid name",old,newname);
+ mm_log (mbx,ERROR);
+ return NIL;
+ }
+ if (s) { /* found a directory delimiter? */
+ if (!s[1]) *s = '\0'; /* ignore trailing delimiter */
+ /* found superior to destination name? */
+ else if ((s != mbx) && ((mbx[1] != ':') || (s != mbx + 2))) {
+ c = s[1]; /* remember character after delimiter */
+ *s = s[1] = '\0'; /* tie off name at delimiter */
+ /* name doesn't exist, create it */
+ if (stat (mbx,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
+ *s = '\\'; /* restore delimiter */
+ if (!dummy_create (stream,mbx)) return NIL;
+ }
+ else *s = '\\'; /* restore delimiter */
+ s[1] = c; /* restore character after delimiter */
+ }
+ }
+ /* rename of non-ex INBOX creates dest */
+ if (!compare_cstring (old,"INBOX") && stat (oldname,&sbuf))
+ return dummy_create (NIL,mbx);
+ if (rename (oldname,mbx)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",old,newname,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ return LONGT; /* return success */
+}
+
+/* Dummy open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *dummy_open (MAILSTREAM *stream)
+{
+ int fd;
+ char err[MAILTMPLEN],tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* OP_PROTOTYPE call */
+ if (!stream) return &dummyproto;
+ err[0] = '\0'; /* no error message yet */
+ /* can we open the file? */
+ if (!dummy_file (tmp,stream->mailbox))
+ sprintf (err,"Can't open this name: %.80s",stream->mailbox);
+ else if ((fd = open (tmp,O_RDONLY,NIL)) < 0) {
+ /* no, error unless INBOX */
+ if (compare_cstring (stream->mailbox,"INBOX"))
+ sprintf (err,"%.80s: %.80s",strerror (errno),stream->mailbox);
+ }
+ else { /* file had better be empty then */
+ fstat (fd,&sbuf); /* sniff at its size */
+ close (fd);
+ if (sbuf.st_size) /* bogus format if non-empty */
+ sprintf (err,"%.80s (file %.80s) is not in valid mailbox format",
+ stream->mailbox,tmp);
+ }
+ if (err[0]) { /* if an error happened */
+ mm_log (err,stream->silent ? WARN : ERROR);
+ return NIL;
+ }
+ else if (!stream->silent) { /* only if silence not requested */
+ mail_exists (stream,0); /* say there are 0 messages */
+ mail_recent (stream,0); /* and certainly no recent ones! */
+ stream->uid_validity = (unsigned long) time (0);
+ }
+ stream->inbox = T; /* note that it's an INBOX */
+ return stream; /* return success */
+}
+
+
+/* Dummy close
+ * Accepts: MAIL stream
+ * options
+ */
+
+void dummy_close (MAILSTREAM *stream,long options)
+{
+ /* return silently */
+}
+
+/* Dummy ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long dummy_ping (MAILSTREAM *stream)
+{
+ MAILSTREAM *test;
+ /* time to do another test? */
+ if (time (0) >= ((time_t) (stream->gensym + 30))) {
+ /* has mailbox format changed? */
+ if ((test = mail_open (NIL,stream->mailbox,OP_PROTOTYPE)) &&
+ (test->dtb != stream->dtb) &&
+ (test = mail_open (NIL,stream->mailbox,NIL))) {
+ /* preserve some resources */
+ test->original_mailbox = stream->original_mailbox;
+ stream->original_mailbox = NIL;
+ test->sparep = stream->sparep;
+ stream->sparep = NIL;
+ test->sequence = stream->sequence;
+ mail_close ((MAILSTREAM *) /* flush resources used by dummy stream */
+ memcpy (fs_get (sizeof (MAILSTREAM)),stream,
+ sizeof (MAILSTREAM)));
+ /* swap the streams */
+ memcpy (stream,test,sizeof (MAILSTREAM));
+ fs_give ((void **) &test);/* flush test now that copied */
+ /* make sure application knows */
+ mail_exists (stream,stream->recent = stream->nmsgs);
+ }
+ /* still hasn't changed */
+ else stream->gensym = (unsigned long) time (0);
+ }
+ return T;
+}
+
+
+/* Dummy check mailbox
+ * Accepts: MAIL stream
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+void dummy_check (MAILSTREAM *stream)
+{
+ dummy_ping (stream); /* invoke ping */
+}
+
+
+/* Dummy expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ return LONGT;
+}
+
+/* Dummy copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * options
+ * Returns: T if copy successful, else NIL
+ */
+
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) fatal ("Impossible dummy_copy");
+ return NIL;
+}
+
+
+/* Dummy append message string
+ * Accepts: mail stream
+ * destination mailbox
+ * append callback function
+ * data for callback
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd = -1;
+ int e;
+ char tmp[MAILTMPLEN];
+ MAILSTREAM *ts = default_proto (T);
+ if (compare_cstring (mailbox,"INBOX") && dummy_file (tmp,mailbox) &&
+ ((fd = open (tmp,O_RDONLY,NIL)) < 0)) {
+ if ((e = errno) == ENOENT) /* failed, was it no such file? */
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",
+ (long) NIL);
+ sprintf (tmp,"%.80s: %.80s",strerror (e),mailbox);
+ mm_log (tmp,ERROR); /* pass up error */
+ return NIL; /* always fails */
+ }
+ if (fd >= 0) { /* found file? */
+ fstat (fd,&sbuf); /* get its size */
+ close (fd); /* toss out the fd */
+ if (sbuf.st_size) ts = NIL; /* non-empty file? */
+ }
+ if (ts) return (*ts->dtb->append) (stream,mailbox,af,data);
+ sprintf (tmp,"Indeterminate mailbox format: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+}
+
+/* Dummy mail generate file string
+ * Accepts: temporary buffer to write into
+ * mailbox name string
+ * Returns: local file string or NIL if failure
+ */
+
+char *dummy_file (char *dst,char *name)
+{
+ char *s = mailboxfile (dst,name);
+ /* return our standard inbox */
+ return (s && !*s) ? strcpy (dst,sysinbox ()) : s;
+}
+
+
+/* Dummy canonicalize name
+ * Accepts: buffer to write name
+ * reference
+ * pattern
+ * Returns: T if success, NIL if failure
+ */
+
+long dummy_canonicalize (char *tmp,char *ref,char *pat)
+{
+ unsigned long i;
+ char *s,dev[4];
+ /* initially no device */
+ dev[0] = dev[1] = dev[2] = dev[3] = '\0';
+ if (ref) switch (*ref) { /* preliminary reference check */
+ case '{': /* remote names not allowed */
+ return NIL; /* disallowed */
+ case '\0': /* empty reference string */
+ break;
+ default: /* all other names */
+ if (ref[1] == ':') { /* start with device name? */
+ dev[0] = *ref++; dev[1] = *ref++;
+ }
+ break;
+ }
+ if (pat[1] == ':') { /* device name in pattern? */
+ dev[0] = *pat++; dev[1] = *pat++;
+ ref = NIL; /* ignore reference */
+ }
+ switch (*pat) {
+ case '#': /* namespace names */
+ if (mailboxfile (tmp,pat)) strcpy (tmp,pat);
+ else return NIL; /* unknown namespace */
+ break;
+ case '{': /* remote names not allowed */
+ return NIL;
+ case '\\': /* rooted name */
+ ref = NIL; /* ignore reference */
+ break;
+ }
+ /* make sure device names are rooted */
+ if (dev[0] && (*(ref ? ref : pat) != '\\')) dev[2] = '\\';
+ /* build name */
+ sprintf (tmp,"%s%s%s",dev,ref ? ref : "",pat);
+ ucase (tmp); /* force upper case */
+ /* count wildcards */
+ for (i = 0, s = tmp; *s; *s++) if ((*s == '*') || (*s == '%')) ++i;
+ if (i > MAXWILDCARDS) { /* ridiculous wildcarding? */
+ MM_LOG ("Excessive wildcards in LIST/LSUB",ERROR);
+ return NIL;
+ }
+ return T;
+}
diff --git a/imap/src/osdep/nt/env_nt.c b/imap/src/osdep/nt/env_nt.c
new file mode 100644
index 00000000..18bc2369
--- /dev/null
+++ b/imap/src/osdep/nt/env_nt.c
@@ -0,0 +1,774 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: NT environment routines
+ *
+ * Author: Mark Crispin
+ * UW Technology
+ * University of Washington
+ * Seattle, WA 98195
+ * Internet: MRC@Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 15 February 2008
+ */
+
+static char *myUserName = NIL; /* user name */
+static char *myLocalHost = NIL; /* local host name */
+static char *myHomeDir = NIL; /* home directory name */
+static char *myNewsrc = NIL; /* newsrc file name */
+static char *sysInbox = NIL; /* system inbox name */
+static long list_max_level = 5; /* maximum level of list recursion */
+ /* block environment init */
+static short block_env_init = NIL;
+static short no822tztext = NIL; /* disable RFC [2]822 timezone text */
+ /* home namespace */
+static NAMESPACE nshome = {"",'\\',NIL,NIL};
+ /* UNIX other user namespace */
+static NAMESPACE nsother = {"#user.",'\\',NIL,NIL};
+ /* namespace list */
+static NAMESPACE *nslist[3] = {&nshome,&nsother,NIL};
+static long alarm_countdown = 0;/* alarm count down */
+static void (*alarm_rang) (); /* alarm interrupt function */
+static unsigned int rndm = 0; /* initial `random' number */
+static int server_nli = 0; /* server and not logged in */
+static int logtry = 3; /* number of login tries */
+ /* block notification */
+static blocknotify_t mailblocknotify = mm_blocknotify;
+ /* callback to get username */
+static userprompt_t mailusername = NIL;
+static long is_nt = -1; /* T if NT, NIL if not NT, -1 unknown */
+static HINSTANCE netapi = NIL;
+typedef NET_API_STATUS (CALLBACK *GETINFO) (LPCWSTR,LPCWSTR,DWORD,LPBYTE *);
+static GETINFO getinfo = NIL;
+
+#include "write.c" /* include safe writing routines */
+#include "pmatch.c" /* include wildcard pattern matcher */
+
+
+/* Get all authenticators */
+
+#include "auths.c"
+
+/* Environment manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *env_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_NAMESPACE:
+ ret = (void *) nslist;
+ break;
+ case SET_USERPROMPT :
+ mailusername = (userprompt_t) value;
+ case GET_USERPROMPT :
+ ret = (void *) mailusername;
+ break;
+ case SET_HOMEDIR:
+ if (myHomeDir) fs_give ((void **) &myHomeDir);
+ myHomeDir = cpystr ((char *) value);
+ case GET_HOMEDIR:
+ ret = (void *) myHomeDir;
+ break;
+ case SET_LOCALHOST:
+ myLocalHost = cpystr ((char *) value);
+ case GET_LOCALHOST:
+ if (myLocalHost) fs_give ((void **) &myLocalHost);
+ ret = (void *) myLocalHost;
+ break;
+ case SET_NEWSRC:
+ if (myNewsrc) fs_give ((void **) &myNewsrc);
+ myNewsrc = cpystr ((char *) value);
+ case GET_NEWSRC:
+ if (!myNewsrc) { /* set news file name if not defined */
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"%s\\NEWSRC",myhomedir ());
+ myNewsrc = cpystr (tmp);
+ }
+ ret = (void *) myNewsrc;
+ break;
+ case SET_SYSINBOX:
+ if (sysInbox) fs_give ((void **) &sysInbox);
+ sysInbox = cpystr ((char *) value);
+ case GET_SYSINBOX:
+ ret = (void *) sysInbox;
+ break;
+ case SET_LISTMAXLEVEL:
+ list_max_level = (long) value;
+ case GET_LISTMAXLEVEL:
+ ret = (void *) list_max_level;
+ break;
+ case SET_DISABLE822TZTEXT:
+ no822tztext = value ? T : NIL;
+ case GET_DISABLE822TZTEXT:
+ ret = (void *) (no822tztext ? VOIDT : NIL);
+ break;
+ case SET_BLOCKENVINIT:
+ block_env_init = value ? T : NIL;
+ case GET_BLOCKENVINIT:
+ ret = (void *) (block_env_init ? VOIDT : NIL);
+ break;
+ case SET_BLOCKNOTIFY:
+ mailblocknotify = (blocknotify_t) value;
+ case GET_BLOCKNOTIFY:
+ ret = (void *) mailblocknotify;
+ break;
+ }
+ return ret;
+}
+
+/* Write current time
+ * Accepts: destination string
+ * optional format of day-of-week prefix
+ * format of date and time
+ * flag whether to append symbolic timezone
+ */
+
+static void do_date (char *date,char *prefix,char *fmt,int suffix)
+{
+ time_t tn = time (0);
+ struct tm *t = gmtime (&tn);
+ int zone = t->tm_hour * 60 + t->tm_min;
+ int julian = t->tm_yday;
+ t = localtime (&tn); /* get local time now */
+ /* minus UTC minutes since midnight */
+ zone = t->tm_hour * 60 + t->tm_min - zone;
+ /* julian can be one of:
+ * 36x local time is December 31, UTC is January 1, offset -24 hours
+ * 1 local time is 1 day ahead of UTC, offset +24 hours
+ * 0 local time is same day as UTC, no offset
+ * -1 local time is 1 day behind UTC, offset -24 hours
+ * -36x local time is January 1, UTC is December 31, offset +24 hours
+ */
+ if (julian = t->tm_yday -julian)
+ zone += ((julian < 0) == (abs (julian) == 1)) ? -24*60 : 24*60;
+ if (prefix) { /* want day of week? */
+ sprintf (date,prefix,days[t->tm_wday]);
+ date += strlen (date); /* make next sprintf append */
+ }
+ /* output the date */
+ sprintf (date,fmt,t->tm_mday,months[t->tm_mon],t->tm_year+1900,
+ t->tm_hour,t->tm_min,t->tm_sec,zone/60,abs (zone) % 60);
+ if (suffix) { /* append timezone suffix if desired */
+ char *tz;
+ tzset (); /* get timezone from TZ environment stuff */
+ tz = tzname[daylight ? (((struct tm *) t)->tm_isdst > 0) : 0];
+ if (tz && tz[0]) {
+ char *s;
+ for (s = tz; *s; s++) if (*s & 0x80) return;
+ sprintf (date + strlen (date)," (%.50s)",tz);
+ }
+ }
+}
+
+
+/* Write current time in RFC 822 format
+ * Accepts: destination string
+ */
+
+void rfc822_date (char *date)
+{
+ do_date (date,"%s, ","%d %s %d %02d:%02d:%02d %+03d%02d",
+ no822tztext ? NIL : T);
+}
+
+
+/* Write current time in fixed-width RFC 822 format
+ * Accepts: destination string
+ */
+
+void rfc822_fixed_date (char *date)
+{
+ do_date (date,NIL,"%02d %s %4d %02d:%02d:%02d %+03d%02d",NIL);
+}
+
+
+/* Write current time in internal format
+ * Accepts: destination string
+ */
+
+void internal_date (char *date)
+{
+ do_date (date,NIL,"%02d-%s-%d %02d:%02d:%02d %+03d%02d",NIL);
+}
+
+/* Return random number
+ */
+
+long random (void)
+{
+ if (!rndm) srand (rndm = (unsigned) time (0L));
+ return (long) rand ();
+}
+
+
+/* Set alarm timer
+ * Accepts: new value
+ * Returns: old alarm value
+ */
+
+long alarm (long seconds)
+{
+ long ret = alarm_countdown;
+ alarm_countdown = seconds;
+ return ret;
+}
+
+
+/* The clock ticked
+ */
+
+void CALLBACK clock_ticked (UINT IDEvent,UINT uReserved,DWORD dwUser,
+ DWORD dwReserved1,DWORD dwReserved2)
+{
+ if (alarm_rang && !--alarm_countdown) (*alarm_rang) ();
+}
+
+/* Initialize server
+ * Accepts: server name for syslog or NIL
+ * /etc/services service name or NIL
+ * alternate /etc/services service name or NIL
+ * clock interrupt handler
+ * kiss-of-death interrupt handler
+ * hangup interrupt handler
+ * termination interrupt handler
+ */
+
+void server_init (char *server,char *service,char *sslservice,
+ void *clkint,void *kodint,void *hupint,void *trmint,
+ void *staint)
+{
+ if (!check_nt ()) {
+ if (!auth_md5.server) fatal ("Can't run on Windows without MD5 database");
+ server_nli = T; /* Windows server not logged in */
+ }
+ /* only do this if for init call */
+ if (server && service && sslservice) {
+ long port;
+ struct servent *sv;
+ /* set server name in syslog */
+ openlog (server,LOG_PID,LOG_MAIL);
+ fclose (stderr); /* possibly save a process ID */
+ /* Use SSL if SSL service, or if server starts with "s" and not service */
+ if (((port = tcp_serverport ()) >= 0)) {
+ if ((sv = getservbyname (service,"tcp")) && (port == ntohs (sv->s_port)))
+ syslog (LOG_DEBUG,"%s service init from %s",service,tcp_clientaddr ());
+ else if ((sv = getservbyname (sslservice,"tcp")) &&
+ (port == ntohs (sv->s_port))) {
+ syslog (LOG_DEBUG,"%s SSL service init from %s",sslservice,
+ tcp_clientaddr ());
+ ssl_server_init (server);
+ }
+ else { /* not service or SSL service port */
+ syslog (LOG_DEBUG,"port %ld service init from %s",port,
+ tcp_clientaddr ());
+ if (*server == 's') ssl_server_init (server);
+ }
+ }
+ /* make sure stdout does binary */
+ setmode (fileno (stdin),O_BINARY);
+ setmode (fileno (stdout),O_BINARY);
+ }
+ alarm_rang = clkint; /* note the clock interrupt */
+ timeBeginPeriod (1000); /* set the timer interval */
+ timeSetEvent (1000,1000,clock_ticked,NIL,TIME_PERIODIC);
+}
+
+
+/* Wait for stdin input
+ * Accepts: timeout in seconds
+ * Returns: T if have input on stdin, else NIL
+ */
+
+long server_input_wait (long seconds)
+{
+ fd_set rfd,efd;
+ struct timeval tmo;
+ FD_ZERO (&rfd);
+ FD_ZERO (&efd);
+ FD_SET (0,&rfd);
+ FD_SET (0,&efd);
+ tmo.tv_sec = seconds; tmo.tv_usec = 0;
+ return select (1,&rfd,0,&efd,&tmo) ? LONGT : NIL;
+}
+
+/* Server log in
+ * Accepts: user name string
+ * password string
+ * authenticating user name string
+ * argument count
+ * argument vector
+ * Returns: T if password validated, NIL otherwise
+ */
+
+static int gotprivs = NIL; /* once-only flag to grab privileges */
+
+long server_login (char *user,char *pass,char *authuser,int argc,char *argv[])
+{
+ HANDLE hdl;
+ LUID tcbpriv;
+ TOKEN_PRIVILEGES tkp;
+ char *s;
+ /* need to get privileges? */
+ if (!gotprivs++ && check_nt ()) {
+ /* hack for inetlisn */
+ if (argc >= 2) myClientHost = argv[1];
+ /* get process token and TCB priv value */
+ if (!(OpenProcessToken (GetCurrentProcess (),
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,&hdl) &&
+ LookupPrivilegeValue ((LPSTR) NIL,SE_TCB_NAME,&tcbpriv)))
+ return NIL;
+ tkp.PrivilegeCount = 1; /* want to enable this privilege */
+ tkp.Privileges[0].Luid = tcbpriv;
+ tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+ /* enable it */
+ AdjustTokenPrivileges (hdl,NIL,&tkp,sizeof (TOKEN_PRIVILEGES),
+ (PTOKEN_PRIVILEGES) NIL,(PDWORD) NIL);
+ /* make sure it won */
+ if (GetLastError() != ERROR_SUCCESS) return NIL;
+ }
+
+ /* cretins still haven't given up */
+ if ((strlen (user) >= MAILTMPLEN) ||
+ (authuser && (strlen (authuser) >= MAILTMPLEN)))
+ syslog (LOG_ALERT,"SYSTEM BREAK-IN ATTEMPT, host=%.80s",tcp_clienthost ());
+ else if (logtry > 0) { /* still have available logins? */
+ /* authentication user not supported */
+ if (authuser && *authuser && compare_cstring (authuser,user))
+ mm_log ("Authentication id must match authorization id",ERROR);
+ if (check_nt ()) { /* NT: authserver_login() call not supported */
+ if (!pass) mm_log ("Unsupported authentication mechanism",ERROR);
+ else if (( /* try to login and impersonate the guy */
+#ifdef LOGIN32_LOGON_NETWORK
+ LogonUser (user,".",pass,LOGON32_LOGON_NETWORK,
+ LOGON32_PROVIDER_DEFAULT,&hdl) ||
+#endif
+ LogonUser (user,".",pass,LOGON32_LOGON_INTERACTIVE,
+ LOGON32_PROVIDER_DEFAULT,&hdl) ||
+ LogonUser (user,".",pass,LOGON32_LOGON_BATCH,
+ LOGON32_PROVIDER_DEFAULT,&hdl) ||
+ LogonUser (user,".",pass,LOGON32_LOGON_SERVICE,
+ LOGON32_PROVIDER_DEFAULT,&hdl)) &&
+ ImpersonateLoggedOnUser (hdl)) return env_init (user,NIL);
+ }
+ else { /* Win9x: done if from authserver_login() */
+ if (!pass) server_nli = NIL;
+ /* otherwise check MD5 database */
+ else if (s = auth_md5_pwd (user)) {
+ /* change NLI state based on pwd match */
+ server_nli = strcmp (s,pass);
+ memset (s,0,strlen (s));/* erase sensitive information */
+ fs_give ((void **) &s); /* flush erased password */
+ }
+ /* success if no longer NLI */
+ if (!server_nli) return env_init (user,NIL);
+ }
+ }
+ s = (logtry-- > 0) ? "Login failure" : "Excessive login attempts";
+ /* note the failure in the syslog */
+ syslog (LOG_INFO,"%s user=%.80s host=%.80s",s,user,tcp_clienthost ());
+ sleep (3); /* slow down possible cracker */
+ return NIL;
+}
+
+/* Authenticated server log in
+ * Accepts: user name string
+ * authentication user name string
+ * argument count
+ * argument vector
+ * Returns: T if password validated, NIL otherwise
+ */
+
+long authserver_login (char *user,char *authuser,int argc,char *argv[])
+{
+ return server_login (user,NIL,authuser,argc,argv);
+}
+
+
+/* Log in as anonymous daemon
+ * Accepts: argument count
+ * argument vector
+ * Returns: T if successful, NIL if error
+ */
+
+long anonymous_login (int argc,char *argv[])
+{
+ return server_login ("Guest",NIL,NIL,argc,argv);
+}
+
+
+/* Initialize environment
+ * Accepts: user name
+ * home directory, or NIL to use default
+ * Returns: T, always
+ */
+
+long env_init (char *user,char *home)
+{
+ /* don't init if blocked */
+ if (block_env_init) return LONGT;
+ if (myUserName) fatal ("env_init called twice!");
+ myUserName = cpystr (user); /* remember user name */
+ if (!myHomeDir) /* only if home directory not set up yet */
+ myHomeDir = (home && *home) ? cpystr (home) : win_homedir (user);
+ return T;
+}
+
+/* Check if NT
+ * Returns: T if NT, NIL if Win9x
+ */
+
+int check_nt (void)
+{
+ if (is_nt < 0) { /* not yet set up? */
+ OSVERSIONINFO ver;
+ ver.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
+ GetVersionEx (&ver);
+ is_nt = (ver.dwPlatformId == VER_PLATFORM_WIN32_NT) ? T : NIL;
+ }
+ return is_nt;
+}
+
+
+/* Return Windows home directory
+ * Accepts: user name
+ * Returns: home directory
+ */
+
+char *win_homedir (char *user)
+{
+ char *s,*t,tmp[MAILTMPLEN];
+ PUSER_INFO_1 ui;
+ /* Win9x default */
+ if (!check_nt ()) sprintf (tmp,"%s\\My Documents",defaultDrive ());
+ /* get from user info on NT */
+ else if ((netapi || (netapi = LoadLibrary ("netapi32.dll"))) &&
+ (getinfo ||
+ (getinfo = (GETINFO) GetProcAddress (netapi,"NetUserGetInfo"))) &&
+ MultiByteToWideChar (CP_ACP,0,user,strlen (user) + 1,
+ (WCHAR *) tmp,MAILTMPLEN) &&
+ !(*getinfo) (NIL,(LPWSTR) &tmp,1,(LPBYTE *) &ui) &&
+ WideCharToMultiByte (CP_ACP,0,ui->usri1_home_dir,-1,
+ tmp,MAILTMPLEN,NIL,NIL) && tmp[0]) {
+ /* make sure doesn't end with delimiter */
+ if ((*(s = tmp + strlen (tmp) - 1) == '\\') || (*s == '/')) *s = '\0';
+ }
+ /* no home dir, found Win2K user profile? */
+ else if ((s = getenv ("USERPROFILE")) && (t = strrchr (s,'\\'))) {
+ strncpy (tmp,s,t-s); /* copy up to user name */
+ sprintf (tmp+(t-s),"\\%.100s\\My Documents",user);
+ }
+ /* last resort NT default */
+ else sprintf (tmp,"%s\\users\\default",defaultDrive ());
+ return cpystr (tmp);
+}
+
+
+/* Return default drive
+ * Returns: default drive
+ */
+
+static char *defaultDrive (void)
+{
+ char *s = getenv ("SystemDrive");
+ return (s && *s) ? s : "C:";
+}
+
+/* Return my user name
+ * Accepts: pointer to optional flags
+ * Returns: my user name
+ */
+
+char *myusername_full (unsigned long *flags)
+{
+ UCHAR usr[MAILTMPLEN];
+ DWORD len = MAILTMPLEN;
+ char *user,*path,*d,*p,pth[MAILTMPLEN];
+ char *ret = "SYSTEM";
+ /* get user name if don't have it yet */
+ if (!myUserName && !server_nli &&
+ /* use callback, else logon name */
+ ((mailusername && (user = (char *) (*mailusername) ())) ||
+ (GetUserName (usr,&len) && _stricmp (user = (char *) usr,"SYSTEM")))) {
+ if (block_env_init) { /* don't env_init if blocked */
+ if (flags) *flags = MU_LOGGEDIN;
+ return user;
+ }
+ /* try HOMEPATH, then HOME */
+ if (p = getenv ("HOMEPATH"))
+ sprintf (path = pth,"%s%s",
+ (d = getenv ("HOMEDRIVE")) ? d : defaultDrive (),p);
+ else if (!(path = getenv ("HOME")))
+ sprintf (path = pth,"%s\\My Documents",defaultDrive ());
+ /* make sure doesn't end with delimiter */
+ if ((*(p = path + strlen (path) -1) == '\\') || (*p == '/')) *p = '\0';
+ env_init (user,path); /* initialize environment */
+ }
+ if (myUserName) { /* logged in? */
+ if (flags) /* Guest is an anonymous user */
+ *flags = _stricmp (myUserName,"Guest") ? MU_LOGGEDIN : MU_ANONYMOUS;
+ ret = myUserName; /* return user name */
+ }
+ else if (flags) *flags = MU_NOTLOGGEDIN;
+ return ret;
+}
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost (void)
+{
+ if (!myLocalHost) {
+ char tmp[MAILTMPLEN];
+ if (!wsa_initted++) { /* init Windows Sockets */
+ WSADATA wsock;
+ if (WSAStartup (WINSOCK_VERSION,&wsock)) {
+ wsa_initted = 0;
+ return "random-pc"; /* try again later? */
+ }
+ }
+ myLocalHost = cpystr ((gethostname (tmp,MAILTMPLEN-1) == SOCKET_ERROR) ?
+ "random-pc" : tcp_canonical (tmp));
+ }
+ return myLocalHost;
+}
+
+/* Return my home directory name
+ * Returns: my home directory name
+ */
+
+char *myhomedir ()
+{
+ if (!myHomeDir) myusername ();/* initialize if first time */
+ return myHomeDir ? myHomeDir : "";
+}
+
+
+/* Return system standard INBOX
+ * Accepts: buffer string
+ */
+
+char *sysinbox ()
+{
+ char tmp[MAILTMPLEN];
+ if (!sysInbox) { /* initialize if first time */
+ if (check_nt ()) sprintf (tmp,MAILFILE,myUserName);
+ else sprintf (tmp,"%s\\INBOX",myhomedir ());
+ sysInbox = cpystr (tmp); /* system inbox is from mail spool */
+ }
+ return sysInbox;
+}
+
+
+/* Return mailbox directory name
+ * Accepts: destination buffer
+ * directory prefix
+ * name in directory
+ * Returns: file name or NIL if error
+ */
+
+char *mailboxdir (char *dst,char *dir,char *name)
+{
+ char tmp[MAILTMPLEN];
+ if (dir || name) { /* if either argument provided */
+ if (dir) {
+ if (strlen (dir) > NETMAXMBX) return NIL;
+ strcpy (tmp,dir); /* write directory prefix */
+ }
+ else tmp[0] = '\0'; /* otherwise null string */
+ if (name) {
+ if (strlen (name) > NETMAXMBX) return NIL;
+ strcat (tmp,name); /* write name in directory */
+ }
+ /* validate name, return its name */
+ if (!mailboxfile (dst,tmp)) return NIL;
+ }
+ else strcpy (dst,myhomedir());/* no arguments, wants home directory */
+ return dst; /* return the name */
+}
+
+/* Return mailbox file name
+ * Accepts: destination buffer
+ * mailbox name
+ * Returns: file name or empty string for driver-selected INBOX or NIL if error
+ */
+
+char *mailboxfile (char *dst,char *name)
+{
+ char homedev[3];
+ char *dir = myhomedir ();
+ if (dir[0] && isalpha (dir[0]) && (dir[1] == ':')) {
+ homedev[0] = dir[0]; /* copy home device */
+ homedev[1] = dir[1];
+ homedev[2] = '\0';
+ }
+ else homedev[0] = '\0'; /* ??no home device?? */
+ *dst = '\0'; /* default to empty string */
+ /* check for INBOX */
+ if (!compare_cstring (name,"INBOX"));
+ /* reject names with / */
+ else if (strchr (name,'/')) dst = NIL;
+ else switch (*name) {
+ case '#': /* namespace names */
+ if (((name[1] == 'u') || (name[1] == 'U')) &&
+ ((name[2] == 's') || (name[2] == 'S')) &&
+ ((name[3] == 'e') || (name[3] == 'E')) &&
+ ((name[4] == 'r') || (name[4] == 'R')) && (name[5] == '.')) {
+ /* copy user name to destination buffer */
+ for (dir = dst,name += 6; *name && (*name != '\\'); *dir++ = *name++);
+ *dir++ = '\0'; /* tie off user name */
+ /* look up homedir for user name */
+ if (dir = win_homedir (dst)) {
+ /* build resulting name */
+ sprintf (dst,"%s\\%s",dir,name);
+ fs_give ((void **) &dir);
+ }
+ else dst = NIL;
+ }
+ else dst = NIL; /* unknown namespace name */
+ break;
+ case '\\': /* absolute path on default drive? */
+ sprintf (dst,"%s%s",homedev,name);
+ break;
+ default: /* any other name */
+ if (name[1] == ':') { /* some other drive? */
+ if (name[2] == '\\') strcpy (dst,name);
+ else sprintf (dst,"%c:\\%s",name[0],name+2);
+ }
+ /* build home-directory relative name */
+ else sprintf (dst,"%s\\%s",dir,name);
+ }
+ return dst; /* return it */
+}
+
+/* Lock file name
+ * Accepts: return buffer for file name
+ * file name
+ * locking to be placed on file if non-NIL
+ * Returns: file descriptor of lock or -1 if error
+ */
+
+int lockname (char *lock,char *fname,int op)
+{
+ int ld;
+ char c,*s;
+ /* Win2K and Win98 have TEMP under windir */
+ if (!((s = lockdir (lock,getenv ("windir"),"TEMP")) ||
+ /* NT4, NT3.x and Win95 use one of these */
+ (s = lockdir (lock,getenv ("TEMP"),NIL)) ||
+ (s = lockdir (lock,getenv ("TMP"),NIL)) ||
+ (s = lockdir (lock,getenv ("TMPDIR"),NIL)) ||
+ /* try one of these */
+ (s = lockdir (lock,defaultDrive (),"WINNT\\TEMP")) ||
+ (s = lockdir (lock,defaultDrive (),"WINDOWS\\TEMP")) ||
+ /* C:\TEMP is last resort */
+ (s = lockdir (lock,defaultDrive (),"TEMP")))) {
+ mm_log ("Unable to find temporary directory",ERROR);
+ return -1;
+ }
+ /* generate file name */
+ while (c = *fname++) switch (c) {
+ case '/': case '\\': case ':':
+ *s++ = '!'; /* convert bad chars to ! */
+ break;
+ default:
+ *s++ = c;
+ break;
+ }
+ *s++ = c; /* tie off name */
+ /* get the lock */
+ if (((ld = open (lock,O_BINARY|O_RDWR|O_CREAT,S_IREAD|S_IWRITE)) >= 0) && op)
+ flock (ld,op); /* apply locking function */
+ return ld; /* return locking file descriptor */
+}
+
+/* Build lock directory, check to see if it exists
+ * Accepts: return buffer for lock directory
+ * first part of possible name
+ * optional second part
+ * Returns: pointer to end of buffer if buffer has a good name, else NIL
+ */
+
+char *lockdir (char *lock,char *first,char *last)
+{
+ struct stat sbuf;
+ char c,*s;
+ if (first && *first) { /* first part must be non-NIL */
+ /* copy first part */
+ for (s = lock; c = *first++; *s++ = (c == '/') ? '\\' : c);
+ if (last && *last) { /* copy last part if specified */
+ /* write trailing \ in case not in first */
+ if (s[-1] != '\\') *s++ = '\\';
+ while (c = *last++) *s++ = (c == '/') ? '\\' : c;
+ }
+ if (s[-1] == '\\') --s; /* delete trailing \ if any */
+ *s = s[1] = '\0'; /* tie off name at this point */
+ if (!stat (lock,&sbuf)) { /* does the name exist? */
+ *s++ = '\\'; /* yes, reinstall trailing \ */
+ return s; /* return the name */
+ }
+ }
+ return NIL; /* failed */
+}
+
+
+/* Unlock file descriptor
+ * Accepts: file descriptor
+ * lock file name from lockfd()
+ */
+
+void unlockfd (int fd,char *lock)
+{
+ flock (fd,LOCK_UN); /* unlock it */
+ close (fd); /* close it */
+}
+
+
+/* Determine default prototype stream to user
+ * Accepts: type (NIL for create, T for append)
+ * Returns: default prototype stream
+ */
+
+MAILSTREAM *default_proto (long type)
+{
+ extern MAILSTREAM CREATEPROTO,APPENDPROTO;
+ return type ? &APPENDPROTO : &CREATEPROTO;
+}
+
+/* Default block notify routine
+ * Accepts: reason for calling
+ * data
+ * Returns: data
+ */
+
+void *mm_blocknotify (int reason,void *data)
+{
+ void *ret = data;
+ switch (reason) {
+ case BLOCK_SENSITIVE: /* entering sensitive code */
+ ret = (void *) alarm (0);
+ break;
+ case BLOCK_NONSENSITIVE: /* exiting sensitive code */
+ if ((unsigned int) data) alarm ((unsigned int) data);
+ break;
+ default: /* ignore all other reasons */
+ break;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/nt/env_nt.h b/imap/src/osdep/nt/env_nt.h
new file mode 100644
index 00000000..95575246
--- /dev/null
+++ b/imap/src/osdep/nt/env_nt.h
@@ -0,0 +1,68 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: NT environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+#define SUBSCRIPTIONFILE(t) sprintf (t,"%s\\MAILBOX.LST",myhomedir ())
+#define SUBSCRIPTIONTEMP(t) sprintf (t,"%s\\MAILBOX.TMP",myhomedir ())
+
+/* Note: the \CRAM-MD5.PWD file only works on DOS-based Windows (Win9x/Me)
+ * and not on NT-based Windows (WinNT/2K/XP). If installed on NT-based
+ * Windows, servers will advertise CRAM-MD5 authentication but it will
+ * never succeed.
+ */
+
+#define MD5ENABLE "\\cram-md5.pwd"
+
+#define L_SET SEEK_SET
+
+/* Function prototypes */
+
+#include "env.h"
+
+void rfc822_fixed_date (char *date);
+long env_init (char *user,char *home);
+int check_nt (void);
+char *win_homedir (char *user);
+static char *defaultDrive (void);
+char *myusername_full (unsigned long *flags);
+#define MU_LOGGEDIN 0
+#define MU_NOTLOGGEDIN 1
+#define MU_ANONYMOUS 2
+#define myusername() \
+ myusername_full (NIL)
+char *sysinbox ();
+char *mailboxdir (char *dst,char *dir,char *name);
+int lockname (char *lock,char *fname,int op);
+char *lockdir (char *lock,char *first,char *last);
+void unlockfd (int fd,char *lock);
+long safe_write (int fd,char *buf,long nbytes);
+void *mm_blocknotify (int reason,void *data);
+long random ();
+#if _MSC_VER < 700
+#define getpid random
+#endif
diff --git a/imap/src/osdep/nt/fdstring.c b/imap/src/osdep/nt/fdstring.c
new file mode 100644
index 00000000..7a491f7d
--- /dev/null
+++ b/imap/src/osdep/nt/fdstring.c
@@ -0,0 +1,99 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: File descriptor string routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 April 1997
+ * Last Edited: 4 April 2007
+ */
+
+#include "mail.h"
+#include "osdep.h"
+#include "misc.h"
+#include "fdstring.h"
+
+/* String driver for fd stringstructs */
+
+static void fd_string_init (STRING *s,void *data,unsigned long size);
+static char fd_string_next (STRING *s);
+static void fd_string_setpos (STRING *s,unsigned long i);
+
+STRINGDRIVER fd_string = {
+ fd_string_init, /* initialize string structure */
+ fd_string_next, /* get next byte in string structure */
+ fd_string_setpos /* set position in string structure */
+};
+
+
+/* Initialize string structure for fd stringstruct
+ * Accepts: string structure
+ * pointer to string
+ * size of string
+ */
+
+static void fd_string_init (STRING *s,void *data,unsigned long size)
+{
+ FDDATA *d = (FDDATA *) data;
+ /* note fd */
+ s->data = (void *) (unsigned long) d->fd;
+ s->data1 = d->pos; /* note file offset */
+ s->size = size; /* note size */
+ s->curpos = s->chunk = d->chunk;
+ s->chunksize = (unsigned long) d->chunksize;
+ s->offset = 0; /* initial position */
+ /* and size of data */
+ s->cursize = min (s->chunksize,size);
+ /* move to that position in the file */
+ lseek (d->fd,d->pos,L_SET);
+ read (d->fd,s->chunk,(size_t) s->cursize);
+}
+
+/* Get next character from fd stringstruct
+ * Accepts: string structure
+ * Returns: character, string structure chunk refreshed
+ */
+
+static char fd_string_next (STRING *s)
+{
+ char c = *s->curpos++; /* get next byte */
+ SETPOS (s,GETPOS (s)); /* move to next chunk */
+ return c; /* return the byte */
+}
+
+
+/* Set string pointer position for fd stringstruct
+ * Accepts: string structure
+ * new position
+ */
+
+static void fd_string_setpos (STRING *s,unsigned long i)
+{
+ if (i > s->size) i = s->size; /* don't permit setting beyond EOF */
+ s->offset = i; /* set new offset */
+ s->curpos = s->chunk; /* reset position */
+ /* set size of data */
+ if (s->cursize = min (s->chunksize,SIZE (s))) {
+ /* move to that position in the file */
+ lseek ((long) s->data,s->data1 + s->offset,L_SET);
+ read ((long) s->data,s->curpos,(size_t) s->cursize);
+ }
+}
diff --git a/imap/src/osdep/nt/fdstring.h b/imap/src/osdep/nt/fdstring.h
new file mode 100644
index 00000000..d0a021bf
--- /dev/null
+++ b/imap/src/osdep/nt/fdstring.h
@@ -0,0 +1,39 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: File descriptor string routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 April 1997
+ * Last Edited: 30 August 2006
+ */
+
+/* Driver-dependent data passed to init method */
+
+typedef struct fd_data {
+ int fd; /* file descriptor */
+ unsigned long pos; /* initial position */
+ char *chunk; /* I/O buffer chunk */
+ unsigned long chunksize; /* I/O buffer chunk length */
+} FDDATA;
+
+
+extern STRINGDRIVER fd_string;
diff --git a/imap/src/osdep/nt/fs_nt.c b/imap/src/osdep/nt/fs_nt.c
new file mode 100644
index 00000000..03908328
--- /dev/null
+++ b/imap/src/osdep/nt/fs_nt.c
@@ -0,0 +1,62 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Free storage management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Get a block of free storage
+ * Accepts: size of desired block
+ * Returns: free storage block
+ */
+
+void *fs_get (size_t size)
+{
+ void *block = malloc (size ? size : (size_t) 1);
+ if (!block) fatal ("Out of memory");
+ return (block);
+}
+
+
+/* Resize a block of free storage
+ * Accepts: ** pointer to current block
+ * new size
+ */
+
+void fs_resize (void **block,size_t size)
+{
+ if (!(*block = realloc (*block,size ? size : (size_t) 1)))
+ fatal ("Can't resize memory");
+}
+
+
+/* Return a block of free storage
+ * Accepts: ** pointer to free storage block
+ */
+
+void fs_give (void **block)
+{
+ free (*block);
+ *block = NIL;
+}
diff --git a/imap/src/osdep/nt/ftl_nt.c b/imap/src/osdep/nt/ftl_nt.c
new file mode 100644
index 00000000..9e65ef55
--- /dev/null
+++ b/imap/src/osdep/nt/ftl_nt.c
@@ -0,0 +1,38 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: DOS/VMS/TOPS-20 crash management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Report a fatal error
+ * Accepts: string to output
+ */
+
+void fatal (char *string)
+{
+ mm_fatal (string); /* pass up the string */
+ abort (); /* die horribly */
+}
diff --git a/imap/src/osdep/nt/ip4_nt.c b/imap/src/osdep/nt/ip4_nt.c
new file mode 100644
index 00000000..23d399e1
--- /dev/null
+++ b/imap/src/osdep/nt/ip4_nt.c
@@ -0,0 +1,184 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX IPv4 routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 18 December 2003
+ * Last Edited: 30 August 2006
+ */
+
+#define SADRLEN sizeof (struct sockaddr)
+
+#define SADR4(sadr) ((struct sockaddr_in *) sadr)
+#define SADR4LEN sizeof (struct sockaddr_in)
+#define SADR4ADR(sadr) SADR4 (sadr)->sin_addr
+#define ADR4LEN sizeof (struct in_addr)
+#define SADR4PORT(sadr) SADR4 (sadr)->sin_port
+
+
+/* IP abstraction layer */
+
+char *ip_sockaddrtostring (struct sockaddr *sadr);
+long ip_sockaddrtoport (struct sockaddr *sadr);
+void *ip_stringtoaddr (char *text,size_t *len,int *family);
+struct sockaddr *ip_newsockaddr (size_t *len);
+struct sockaddr *ip_sockaddr (int family,void *adr,size_t adrlen,
+ unsigned short port,size_t *len);
+char *ip_sockaddrtoname (struct sockaddr *sadr);
+void *ip_nametoaddr (char *name,size_t *len,int *family,char **canonical,
+ void **next);
+
+/* Return IP address string from socket address
+ * Accepts: socket address
+ * Returns: IP address as name string
+ */
+
+char *ip_sockaddrtostring (struct sockaddr *sadr)
+{
+ return (sadr->sa_family == PF_INET) ?
+ inet_ntoa (SADR4ADR (sadr)) : "NON-IPv4";
+}
+
+
+/* Return port from socket address
+ * Accepts: socket address
+ * Returns: port number or -1 if can't determine it
+ */
+
+long ip_sockaddrtoport (struct sockaddr *sadr)
+{
+ return (sadr->sa_family == PF_INET) ? ntohs (SADR4PORT (sadr)) : -1;
+}
+
+
+/* Return IP address from string
+ * Accepts: name string
+ * pointer to returned length
+ * pointer to returned address family
+ * Returns: address if valid, length and family updated, or NIL
+ */
+
+void *ip_stringtoaddr (char *text,size_t *len,int *family)
+{
+ unsigned long adr;
+ struct in_addr *ret;
+ /* get address */
+ if ((adr = inet_addr (text)) == -1) ret = NIL;
+ else { /* make in_addr */
+ ret = (struct in_addr *) fs_get (*len = ADR4LEN);
+ *family = AF_INET; /* IPv4 */
+ ret->s_addr = adr; /* set address */
+ }
+ return (void *) ret;
+}
+
+/* Create a maximum-size socket address
+ * Accepts: pointer to return maximum socket address length
+ * Returns: new, empty socket address of maximum size
+ */
+
+struct sockaddr *ip_newsockaddr (size_t *len)
+{
+ return (struct sockaddr *) memset (fs_get (SADRLEN),0,*len = SADRLEN);
+}
+
+
+/* Stuff a socket address
+ * Accepts: address family
+ * IPv4 address
+ * length of address (always 4 in IPv4)
+ * port number
+ * pointer to return socket address length
+ * Returns: socket address or NIL if error
+ */
+
+struct sockaddr *ip_sockaddr (int family,void *adr,size_t adrlen,
+ unsigned short port,size_t *len)
+{
+ struct sockaddr *sadr = ip_newsockaddr (len);
+ switch (family) { /* build socket address based upon family */
+ case AF_INET: /* IPv4 */
+ sadr->sa_family = PF_INET;
+ /* copy host address */
+ memcpy (&SADR4ADR (sadr),adr,adrlen);
+ /* copy port number in network format */
+ SADR4PORT (sadr) = htons (port);
+ *len = SADR4LEN;
+ break;
+ default: /* non-IP?? */
+ sadr->sa_family = PF_UNSPEC;
+ break;
+ }
+ return sadr;
+}
+
+/* Return name from socket address
+ * Accepts: socket address
+ * Returns: canonical name for that address or NIL if none
+ */
+
+char *ip_sockaddrtoname (struct sockaddr *sadr)
+{
+ struct hostent *he;
+ return ((sadr->sa_family == PF_INET) &&
+ (he = gethostbyaddr ((char *) &SADR4ADR (sadr),ADR4LEN,AF_INET))) ?
+ (char *) he->h_name : NIL;
+}
+
+
+/* Return address from name
+ * Accepts: name or NIL to return next address
+ * pointer to previous/returned length
+ * pointer to previous/returned address family
+ * pointer to previous/returned canonical name
+ * pointer to previous/return state for next-address calls
+ * Returns: address with length/family/canonical updated if needed, or NIL
+ */
+
+void *ip_nametoaddr (char *name,size_t *len,int *family,char **canonical,
+ void **next)
+{
+ char **adl,tmp[MAILTMPLEN];
+ struct hostent *he;
+ if (name) { /* first lookup? */
+ /* yes, do case-independent lookup */
+ if ((strlen (name) < MAILTMPLEN) &&
+ (he = gethostbyname (lcase (strcpy (tmp,name))))) {
+ adl = he->h_addr_list;
+ if (len) *len = he->h_length;
+ if (family) *family = he->h_addrtype;
+ if (canonical) *canonical = (char *) he->h_name;
+ if (next) *next = (void *) adl;
+ }
+ else { /* error */
+ adl = NIL;
+ if (len) *len = 0;
+ if (family) *family = 0;
+ if (canonical) *canonical = NIL;
+ if (next) *next = NIL;
+ }
+ }
+ /* return next in series */
+ else if (next && (adl = (char **) *next)) *next = ++adl;
+ else adl = NIL; /* failure */
+ return adl ? (void *) *adl : NIL;
+}
diff --git a/imap/src/osdep/nt/ip6_nt.c b/imap/src/osdep/nt/ip6_nt.c
new file mode 100644
index 00000000..c15dfbc0
--- /dev/null
+++ b/imap/src/osdep/nt/ip6_nt.c
@@ -0,0 +1,288 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX IPv6 routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 18 December 2003
+ * Last Edited: 30 August 2006
+ */
+
+
+/*
+ * There is some amazingly bad design in IPv6 sockets.
+ *
+ * Supposedly, the new getnameinfo() and getaddrinfo() functions create an
+ * abstraction that is not dependent upon IPv4 or IPv6. However, the
+ * definition of getnameinfo() requires that the caller pass the length of
+ * the sockaddr instead of deriving it from sa_family. The man page says
+ * that there's an sa_len member in the sockaddr, but actually there isn't.
+ * This means that any caller to getnameinfo() and getaddrinfo() has to know
+ * the size for the protocol family used by that sockaddr.
+ *
+ * The new sockaddr_in6 is bigger than the generic sockaddr (which is what
+ * connect(), accept(), bind(), getpeername(), getsockname(), etc. expect).
+ * Rather than increase the size of sockaddr, there's a new sockaddr_storage
+ * which is only usable for allocating space.
+ */
+
+#define SADRLEN sizeof (struct sockaddr_storage)
+
+#define SADR4(sadr) ((struct sockaddr_in *) sadr)
+#define SADR4LEN sizeof (struct sockaddr_in)
+#define SADR4ADR(sadr) SADR4 (sadr)->sin_addr
+#define ADR4LEN sizeof (struct in_addr)
+#define SADR4PORT(sadr) SADR4 (sadr)->sin_port
+
+#define SADR6(sadr) ((struct sockaddr_in6 *) sadr)
+#define SADR6LEN sizeof (struct sockaddr_in6)
+#define SADR6ADR(sadr) SADR6 (sadr)->sin6_addr
+#define ADR6LEN sizeof (struct in6_addr)
+#define SADR6PORT(sadr) SADR6 (sadr)->sin6_port
+
+
+/* IP abstraction layer */
+
+char *ip_sockaddrtostring (struct sockaddr *sadr);
+long ip_sockaddrtoport (struct sockaddr *sadr);
+void *ip_stringtoaddr (char *text,size_t *len,int *family);
+struct sockaddr *ip_newsockaddr (size_t *len);
+struct sockaddr *ip_sockaddr (int family,void *adr,size_t adrlen,
+ unsigned short port,size_t *len);
+char *ip_sockaddrtoname (struct sockaddr *sadr);
+void *ip_nametoaddr (char *name,size_t *len,int *family,char **canonical,
+ void **next);
+
+/* Return IP address string from socket address
+ * Accepts: socket address
+ * Returns: IP address as name string
+ */
+
+char *ip_sockaddrtostring (struct sockaddr *sadr)
+{
+ static char tmp[NI_MAXHOST];
+ switch (sadr->sa_family) {
+ case PF_INET: /* IPv4 */
+ if (!getnameinfo (sadr,SADR4LEN,tmp,NI_MAXHOST,NIL,NIL,NI_NUMERICHOST))
+ return tmp;
+ break;
+ case PF_INET6: /* IPv6 */
+ if (!getnameinfo (sadr,SADR6LEN,tmp,NI_MAXHOST,NIL,NIL,NI_NUMERICHOST))
+ return tmp;
+ break;
+ }
+ return "NON-IP";
+}
+
+
+/* Return port from socket address
+ * Accepts: socket address
+ * Returns: port number or -1 if can't determine it
+ */
+
+long ip_sockaddrtoport (struct sockaddr *sadr)
+{
+ switch (sadr->sa_family) {
+ case PF_INET:
+ return ntohs (SADR4PORT (sadr));
+ case PF_INET6:
+ return ntohs (SADR6PORT (sadr));
+ }
+ return -1;
+}
+
+/* Return IP address from string
+ * Accepts: name string
+ * pointer to returned length
+ * pointer to returned address family
+ * Returns: address if valid, length and family updated, or NIL
+ */
+
+void *ip_stringtoaddr (char *text,size_t *len,int *family)
+
+{
+ char tmp[MAILTMPLEN];
+ static struct addrinfo *hints;
+ struct addrinfo *ai;
+ void *adr = NIL;
+ if (!hints) { /* hints set up yet? */
+ hints = (struct addrinfo *) /* one-time setup */
+ memset (fs_get (sizeof (struct addrinfo)),0,sizeof (struct addrinfo));
+ hints->ai_family = AF_UNSPEC;/* allow any address family */
+ hints->ai_socktype = SOCK_STREAM;
+ /* numeric name only */
+ hints->ai_flags = AI_NUMERICHOST;
+ }
+ /* case-independent lookup */
+ if (text && (strlen (text) < MAILTMPLEN) &&
+ (!getaddrinfo (lcase (strcpy (tmp,text)),NIL,hints,&ai))) {
+ switch (*family = ai->ai_family) {
+ case AF_INET: /* IPv4 */
+ adr = fs_get (*len = ADR4LEN);
+ memcpy (adr,(void *) &SADR4ADR (ai->ai_addr),*len);
+ break;
+ case AF_INET6: /* IPv6 */
+ adr = fs_get (*len = ADR6LEN);
+ memcpy (adr,(void *) &SADR6ADR (ai->ai_addr),*len);
+ break;
+ }
+ freeaddrinfo (ai); /* free addrinfo */
+ }
+ return adr;
+}
+
+/* Create a maximum-size socket address
+ * Accepts: pointer to return maximum socket address length
+ * Returns: new, empty socket address of maximum size
+ */
+
+struct sockaddr *ip_newsockaddr (size_t *len)
+{
+ return (struct sockaddr *) memset (fs_get (SADRLEN),0,*len = SADRLEN);
+}
+
+
+/* Stuff a socket address
+ * Accepts: address family
+ * IPv4 address
+ * length of address
+ * port number
+ * pointer to return socket address length
+ * Returns: socket address
+ */
+
+struct sockaddr *ip_sockaddr (int family,void *adr,size_t adrlen,
+ unsigned short port,size_t *len)
+{
+ struct sockaddr *sadr = ip_newsockaddr (len);
+ switch (family) { /* build socket address based upon family */
+ case AF_INET: /* IPv4 */
+ sadr->sa_family = PF_INET;
+ /* copy host address */
+ memcpy (&SADR4ADR (sadr),adr,adrlen);
+ /* copy port number in network format */
+ SADR4PORT (sadr) = htons (port);
+ *len = SADR4LEN;
+ break;
+ case AF_INET6: /* IPv6 */
+ sadr->sa_family = PF_INET6;
+ /* copy host address */
+ memcpy (&SADR6ADR (sadr),adr,adrlen);
+ /* copy port number in network format */
+ SADR6PORT (sadr) = htons (port);
+ *len = SADR6LEN;
+ break;
+ default: /* non-IP?? */
+ sadr->sa_family = PF_UNSPEC;
+ break;
+ }
+ return sadr;
+}
+
+/* Return name from socket address
+ * Accepts: socket address
+ * Returns: canonical name for that address or NIL if none
+ */
+
+char *ip_sockaddrtoname (struct sockaddr *sadr)
+{
+ static char tmp[NI_MAXHOST];
+ switch (sadr->sa_family) {
+ case PF_INET: /* IPv4 */
+ if (!getnameinfo (sadr,SADR4LEN,tmp,NI_MAXHOST,NIL,NIL,NI_NAMEREQD))
+ return tmp;
+ break;
+ case PF_INET6: /* IPv6 */
+ if (!getnameinfo (sadr,SADR6LEN,tmp,NI_MAXHOST,NIL,NIL,NI_NAMEREQD))
+ return tmp;
+ break;
+ }
+ return NIL;
+}
+
+/* Return address from name
+ * Accepts: name or NIL to return next address
+ * pointer to previous/returned length
+ * pointer to previous/returned address family
+ * pointer to previous/returned canonical name
+ * pointer to previous/return state for next-address calls
+ * Returns: address with length/family/canonical updated if needed, or NIL
+ */
+
+void *ip_nametoaddr (char *name,size_t *len,int *family,char **canonical,
+ void **next)
+{
+ struct addrinfo *cur = NIL;
+ static struct addrinfo *hints;
+ static struct addrinfo *ai = NIL;
+ static char lcname[MAILTMPLEN];
+ if (!hints) { /* hints set up yet? */
+ hints = (struct addrinfo *) /* one-time setup */
+ memset (fs_get (sizeof (struct addrinfo)),0,sizeof (struct addrinfo));
+ /* allow any address family */
+ hints->ai_family = AF_UNSPEC;
+ hints->ai_socktype = SOCK_STREAM;
+ /* need canonical name */
+ hints->ai_flags = AI_CANONNAME;
+ }
+ if (name) { /* name supplied? */
+ if (ai) {
+ freeaddrinfo (ai); /* free old addrinfo */
+ ai = NIL;
+ }
+ /* case-independent lookup */
+ if ((strlen (name) < MAILTMPLEN) &&
+ (!getaddrinfo (lcase (strcpy (lcname,name)),NIL,hints,&ai))) {
+ cur = ai; /* current block */
+ if (canonical) /* set canonical name */
+ *canonical = cur->ai_canonname ? cur->ai_canonname : lcname;
+ /* remember as next block */
+ if (next) *next = (void *) ai;
+ }
+ else { /* error */
+ cur = NIL;
+ if (len) *len = 0;
+ if (family) *family = 0;
+ if (canonical) *canonical = NIL;
+ if (next) *next = NIL;
+ }
+ }
+ /* return next in series */
+ else if (next && (cur = ((struct addrinfo *) *next)->ai_next)) {
+ *next = cur; /* set as last address */
+ /* set canonical in case changed */
+ if (canonical && cur->ai_canonname) *canonical = cur->ai_canonname;
+ }
+
+ if (cur) { /* got data? */
+ if (family) *family = cur->ai_family;
+ switch (cur->ai_family) {
+ case AF_INET:
+ if (len) *len = ADR4LEN;
+ return (void *) &SADR4ADR (cur->ai_addr);
+ case AF_INET6:
+ if (len) *len = ADR6LEN;
+ return (void *) &SADR6ADR (cur->ai_addr);
+ }
+ }
+ if (len) *len = 0; /* error return */
+ return NIL;
+}
diff --git a/imap/src/osdep/nt/kerb_mit.c b/imap/src/osdep/nt/kerb_mit.c
new file mode 100644
index 00000000..57ede524
--- /dev/null
+++ b/imap/src/osdep/nt/kerb_mit.c
@@ -0,0 +1,74 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MIT Kerberos routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 4 March 2003
+ * Last Edited: 30 August 2006
+ */
+
+#define PROTOTYPE(x) x
+#include <gssapi/gssapi_generic.h>
+#include <gssapi/gssapi_krb5.h>
+
+
+long kerberos_server_valid (void);
+long kerberos_try_kinit (OM_uint32 error);
+char *kerberos_login (char *user,char *authuser,int argc,char *argv[]);
+
+/* Kerberos server valid check
+ * Returns: T if have keytab, NIL otherwise
+ */
+
+long kerberos_server_valid ()
+{
+ return NIL;
+}
+
+
+/* Kerberos check for missing or expired credentials
+ * Returns: T if should suggest running kinit, NIL otherwise
+ */
+
+long kerberos_try_kinit (OM_uint32 error)
+{
+ switch (error) {
+ case KRB5KRB_AP_ERR_TKT_EXPIRED:
+ case KRB5_FCC_NOFILE: /* MIT */
+ case KRB5_CC_NOTFOUND: /* Heimdal */
+ return LONGT;
+ }
+ return NIL;
+}
+
+/* Kerberos server log in
+ * Accepts: authorization ID as user name
+ * authentication ID as Kerberos principal
+ * argument count
+ * argument vector
+ * Returns: logged in user name if logged in, NIL otherwise
+ */
+
+char *kerberos_login (char *user,char *authuser,int argc,char *argv[])
+{
+ return NIL;
+}
diff --git a/imap/src/osdep/nt/kerb_w2k.c b/imap/src/osdep/nt/kerb_w2k.c
new file mode 100644
index 00000000..38d0ce30
--- /dev/null
+++ b/imap/src/osdep/nt/kerb_w2k.c
@@ -0,0 +1,699 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: GSSAPI Kerberos Shim 5 for Windows 2000/XP IMAP Toolkit
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 6 March 2000
+ * Last Edited: 30 August 2006
+ */
+
+/* The purpose of this module is to be a shim, so that the auth_gss.c module
+ * (written for MIT Kerberos) will compile, link, and run with SSPI Kerberos
+ * on Windows 2000 systems.
+ * There is no attempt whatsoever to make this be a complete implementation
+ * of GSSAPI. A number of shortcuts were taken that a real GSSAPI
+ * implementation for SSPI can't do.
+ * Nor is there any attempt to make the types identical with MIT Kerberos;
+ * you can't link this library with object files compiled with the MIT
+ * Kerberos .h files.
+ */
+
+
+/* GSSAPI generic definitions */
+
+
+#define SECURITY_WIN32
+#include <security.h>
+
+
+/* GSSAPI types for which we use SSPI equivalent types */
+
+typedef ULONG OM_uint32;
+typedef PCredHandle gss_cred_id_t;
+typedef ULONG gss_cred_usage_t;
+typedef PCtxtHandle gss_ctx_id_t;
+typedef SEC_CHAR * gss_name_t;
+typedef ULONG gss_qop_t;
+
+
+/* Major status codes */
+
+#define GSS_S_COMPLETE SEC_E_OK
+#define GSS_S_BAD_MECH SEC_E_SECPKG_NOT_FOUND
+#define GSS_S_CONTINUE_NEEDED SEC_I_CONTINUE_NEEDED
+#define GSS_S_CREDENTIALS_EXPIRED SEC_E_CERT_EXPIRED
+#define GSS_S_FAILURE SEC_E_INTERNAL_ERROR
+#define GSS_S_NO_CRED SEC_E_NO_CREDENTIALS
+#define GSS_S_NO_CONTEXT SEC_E_INVALID_HANDLE
+
+
+/* Flag bits for context-level services */
+
+#define GSS_C_DELEG_FLAG ISC_REQ_DELEGATE
+#define GSS_C_MUTUAL_FLAG ISC_REQ_MUTUAL_AUTH
+#define GSS_C_REPLAY_FLAG ISC_REQ_REPLAY_DETECT
+#define GSS_C_SEQUENCE_FLAG ISC_REQ_SEQUENCE_DETECT
+#define GSS_C_CONF_FLAG ISC_REQ_CONFIDENTIALITY
+#define GSS_C_INTEG_FLAG ISC_REQ_INTEGRITY
+
+
+/* Credential usage options */
+
+#define GSS_C_BOTH SECPKG_CRED_BOTH
+#define GSS_C_INITIATE SECPKG_CRED_OUTBOUND
+#define GSS_C_ACCEPT SECPKG_CRED_INBOUND
+
+
+/* Major status codes defined by shim */
+
+#define GSS_S_BAD_BINDINGS 100
+#define GSS_S_BAD_NAME 101
+#define GSS_S_BAD_NAMETYPE 102
+#define GSS_S_BAD_STATUS 103
+
+/* GSSAPI types as used in GSSAPI */
+
+
+/* Buffer */
+
+typedef struct gss_buffer_desc_struct {
+ size_t length;
+ void *value;
+} gss_buffer_desc,*gss_buffer_t;
+
+
+/* Object identifier */
+
+typedef struct gss_OID_desc_struct {
+ OM_uint32 length;
+ void *elements;
+} gss_OID_desc,*gss_OID;
+
+typedef struct gss_OID_set_desc_struct {
+ size_t count;
+ gss_OID elements;
+} gss_OID_set_desc,*gss_OID_set;
+
+
+/* Unused, but needed in prototypes */
+
+typedef void * gss_channel_bindings_t;
+
+
+/* Default constants */
+
+#define GSS_C_EMPTY_BUFFER {0,NIL}
+#define GSS_C_NO_BUFFER ((gss_buffer_t) NIL)
+#define GSS_C_NO_OID ((gss_OID) NIL)
+#define GSS_C_NO_CONTEXT ((gss_ctx_id_t) NIL)
+#define GSS_C_NO_CREDENTIAL ((gss_cred_id_t) NIL)
+#define GSS_C_NO_CHANNEL_BINDINGS ((gss_channel_bindings_t) NIL)
+#define GSS_C_QOP_DEFAULT NIL
+
+
+/* Status code types for gss_display_status */
+
+#define GSS_C_GSS_CODE 1
+#define GSS_C_MECH_CODE 2
+
+
+/* GSSAPI constants */
+
+const gss_OID gss_nt_service_name;
+#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
+const gss_OID gss_mech_krb5;
+const gss_OID_set gss_mech_set_krb5;
+
+/* GSSAPI prototypes */
+
+
+OM_uint32 gss_accept_sec_context (OM_uint32 *minor_status,
+ gss_ctx_id_t *context_handle,
+ gss_cred_id_t acceptor_cred_handle,
+ gss_buffer_t input_token_buffer,
+ gss_channel_bindings_t input_chan_bindings,
+ gss_name_t *src_name,gss_OID *mech_type,
+ gss_buffer_t output_token,
+ OM_uint32 *ret_flags,OM_uint32 *time_rec,
+ gss_cred_id_t *delegated_cred_handle);
+OM_uint32 gss_acquire_cred (OM_uint32 *minor_status,gss_name_t desired_name,
+ OM_uint32 time_req,gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_cred_id_t *output_cred_handle,
+ gss_OID_set *actual_mechs,OM_uint32 *time_rec);
+OM_uint32 gss_delete_sec_context (OM_uint32 *minor_status,
+ gss_ctx_id_t *context_handle,
+ gss_buffer_t output_token);
+OM_uint32 gss_display_name (OM_uint32 *minor_status,gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID *output_name_type);
+OM_uint32 gss_display_status (OM_uint32 *minor_status,OM_uint32 status_value,
+ int status_type,gss_OID mech_type,
+ OM_uint32 *message_context,
+ gss_buffer_t status_string);
+OM_uint32 gss_import_name (OM_uint32 *minor_status,
+ gss_buffer_t input_name_buffer,
+ gss_OID input_name_type,gss_name_t *output_name);
+OM_uint32 gss_init_sec_context (OM_uint32 *minor_status,
+ gss_cred_id_t claimant_cred_handle,
+ gss_ctx_id_t *context_handle,
+ gss_name_t target_name,gss_OID mech_type,
+ OM_uint32 req_flags,OM_uint32 time_req,
+ gss_channel_bindings_t input_chan_bindings,
+ gss_buffer_t input_token,
+ gss_OID *actual_mech_type,
+ gss_buffer_t output_token,OM_uint32 *ret_flags,
+ OM_uint32 *time_rec);
+OM_uint32 gss_release_buffer (OM_uint32 *minor_status,gss_buffer_t buffer);
+OM_uint32 gss_release_cred (OM_uint32 *minor_status,gss_cred_id_t *cred_handle);
+OM_uint32 gss_release_name (OM_uint32 *minor_status,gss_name_t *input_name);
+OM_uint32 gss_wrap (OM_uint32 *minor_status,gss_ctx_id_t context_handle,
+ int conf_req_flag,gss_qop_t qop_req,
+ gss_buffer_t input_message_buffer,int *conf_state,
+ gss_buffer_t output_message_buffer);
+OM_uint32 gss_unwrap (OM_uint32 *minor_status,gss_ctx_id_t context_handle,
+ gss_buffer_t input_message_buffer,
+ gss_buffer_t output_message_buffer,int *conf_state,
+ gss_qop_t *qop_state);
+
+/* Kerberos definitions */
+
+long kerberos_server_valid (void);
+long kerberos_try_kinit (OM_uint32 error);
+char *kerberos_login (char *user,char *authuser,int argc,char *argv[]);
+
+
+#define STRING WINSTRING /* conflict with mail.h */
+#include <NTSecAPI.h>
+
+/* GSSAPI build-in object identifiers */
+
+static gss_OID_desc oids[] = { /* stupid C language makes this necessary */
+ {10,"\052\206\110\206\367\022\001\002\001\004"},
+ {9,"\052\206\110\206\367\022\001\002\002"}
+};
+
+ /* stupid C language ditto */
+static gss_OID_set_desc oidsets[] = {
+ {1,(gss_OID) oids+1}
+};
+
+ /* these are the real OIDs */
+const gss_OID gss_nt_service_name = oids+0;
+const gss_OID gss_mech_krb5 = oids+1;
+const gss_OID_set gss_mech_set_krb5 = oidsets+0;
+
+
+/* Other globals */
+
+ /* substitute for GSS_C_NO_CREDENTIAL */
+static gss_cred_id_t gss_default_cred = NIL;
+
+/* GSSAPI import name (convert to full service principal name)
+ * Accepts: pointer to return minor status
+ * buffer containining input name
+ * type of input name
+ * pointer to return output internal name
+ * Returns: major status, always
+ */
+
+OM_uint32 gss_import_name (OM_uint32 *minor_status,
+ gss_buffer_t input_name_buffer,
+ gss_OID input_name_type,gss_name_t *output_name)
+{
+ OM_uint32 major_status = GSS_S_COMPLETE;
+ TimeStamp expiry;
+ static CredHandle gss_cred;
+ char *s,tmp[MAILTMPLEN];
+ *minor_status = 0; /* never any minor status */
+ if (!gss_default_cred) { /* default credentials set up yet? */
+ if (AcquireCredentialsHandle/* no, acquire them now */
+ (NIL,MICROSOFT_KERBEROS_NAME_A,SECPKG_CRED_OUTBOUND,NIL,NIL,NIL,NIL,
+ &gss_cred,&expiry) != SEC_E_OK) return GSS_S_FAILURE;
+ /* have default credentials now */
+ gss_default_cred = &gss_cred;
+ }
+ /* must be the gss_nt_service_name format */
+ if (input_name_type != gss_nt_service_name)
+ major_status = GSS_S_BAD_NAMETYPE;
+ /* name must be of sane length */
+ else if (input_name_buffer->length > (MAILTMPLEN/2))
+ major_status = GSS_S_BAD_NAME;
+ else { /* copy name */
+ memcpy (tmp,input_name_buffer->value,input_name_buffer->length);
+ tmp[input_name_buffer->length] = '\0';
+ if (s = strchr (tmp,'@')) { /* find service/host/delimiter */
+ *s = '/'; /* convert to full service principal name */
+ *output_name = cpystr (tmp);
+ }
+ else major_status = GSS_S_BAD_NAME;
+ }
+ return major_status;
+}
+
+/* GSSAPI Initialize security context
+ * Accepts: pointer to return minor status
+ * claimant credential handle
+ * context (NIL means "none assigned yet")
+ * desired principal
+ * desired mechanisms
+ * required context attributes
+ * desired lifetime
+ * input channel bindings
+ * input token buffer
+ * pointer to return mechanism type
+ * buffer to return output token
+ * pointer to return flags
+ * pointer to return context lifetime
+ * Returns: major status, always
+ */
+
+OM_uint32 gss_init_sec_context (OM_uint32 *minor_status,
+ gss_cred_id_t claimant_cred_handle,
+ gss_ctx_id_t *context_handle,
+ gss_name_t target_name,gss_OID mech_type,
+ OM_uint32 req_flags,OM_uint32 time_req,
+ gss_channel_bindings_t input_chan_bindings,
+ gss_buffer_t input_token,
+ gss_OID *actual_mech_type,
+ gss_buffer_t output_token,OM_uint32 *ret_flags,
+ OM_uint32 *time_rec)
+{
+ OM_uint32 i;
+ OM_uint32 major_status;
+ TimeStamp expiry;
+ SecBuffer ibuf[1],obuf[1];
+ SecBufferDesc ibufs,obufs;
+ *minor_status = 0; /* never any minor status */
+ /* error if non-default time requested */
+ if (time_req) return GSS_S_FAILURE;
+ if (mech_type && memcmp (mech_type,gss_mech_krb5,sizeof (gss_OID)))
+ return GSS_S_BAD_MECH;
+ /* ditto if any channel bindings */
+ if (input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS)
+ return GSS_S_BAD_BINDINGS;
+
+ /* apply default credential if necessary */
+ if (claimant_cred_handle == GSS_C_NO_CREDENTIAL)
+ claimant_cred_handle = gss_default_cred;
+ /* create output buffer storage as needed */
+ req_flags |= ISC_REQ_ALLOCATE_MEMORY;
+ /* make output buffer */
+ obuf[0].BufferType = SECBUFFER_TOKEN;
+ obuf[0].cbBuffer = 0; obuf[0].pvBuffer = NIL;
+ /* output buffer descriptor */
+ obufs.ulVersion = SECBUFFER_VERSION;
+ obufs.cBuffers = 1;
+ obufs.pBuffers = obuf;
+ /* first time caller? */
+ if (*context_handle == GSS_C_NO_CONTEXT) {
+ /* yes, set up output context handle */
+ PCtxtHandle ctx = (PCtxtHandle) fs_get (sizeof (CtxtHandle));
+ major_status = InitializeSecurityContext (claimant_cred_handle,NIL,
+ target_name,req_flags,0,
+ SECURITY_NETWORK_DREP,NIL,0,ctx,
+ &obufs,
+ ret_flags ? ret_flags : &i,
+ &expiry);
+ *context_handle = ctx; /* return updated context */
+ }
+ else { /* no, make SSPI buffer from GSSAPI buffer */
+ ibuf[0].BufferType = obuf[0].BufferType = SECBUFFER_TOKEN;
+ ibuf[0].cbBuffer = input_token->length;
+ ibuf[0].pvBuffer = input_token->value;
+ /* input buffer descriptor */
+ ibufs.ulVersion = SECBUFFER_VERSION;
+ ibufs.cBuffers = 1;
+ ibufs.pBuffers = ibuf;
+ major_status = InitializeSecurityContext (claimant_cred_handle,
+ *context_handle,target_name,
+ req_flags,0,
+ SECURITY_NETWORK_DREP,&ibufs,0,
+ *context_handle,&obufs,
+ ret_flags ? ret_flags : &i,
+ &expiry);
+ }
+ /* return output */
+ output_token->value = obuf[0].pvBuffer;
+ output_token->length = obuf[0].cbBuffer;
+ /* in case client wanted lifetime returned */
+ if (time_rec) *time_rec = expiry.LowPart;
+ return major_status;
+}
+
+/* GSSAPI display status text
+ * Accepts: pointer to return minor status
+ * status to display
+ * status type
+ * message context for continuation
+ * buffer to write status string
+ * Returns: major status, always
+ */
+
+OM_uint32 gss_display_status (OM_uint32 *minor_status,OM_uint32 status_value,
+ int status_type,gss_OID mech_type,
+ OM_uint32 *message_context,
+ gss_buffer_t status_string)
+{
+ char *s,tmp[MAILTMPLEN];
+ *minor_status = 0; /* never any minor status */
+ if (*message_context) return GSS_S_FAILURE;
+ switch (status_type) { /* what type of status code? */
+ case GSS_C_GSS_CODE: /* major_status */
+ switch (status_value) { /* analyze status value */
+ case GSS_S_FAILURE:
+ s = "Unspecified failure"; break;
+ case GSS_S_CREDENTIALS_EXPIRED:
+ s = "Credentials expired"; break;
+ case GSS_S_BAD_BINDINGS:
+ s = "Bad bindings"; break;
+ case GSS_S_BAD_MECH:
+ s = "Bad mechanism type"; break;
+ case GSS_S_BAD_NAME:
+ s = "Bad name"; break;
+ case GSS_S_BAD_NAMETYPE:
+ s = "Bad name type"; break;
+ case GSS_S_BAD_STATUS:
+ s = "Bad status"; break;
+ case GSS_S_NO_CONTEXT:
+ s = "Invalid context handle"; break;
+ case GSS_S_NO_CRED:
+ s = "Unable to authenticate to Kerberos service";
+ mail_parameters (NIL,DISABLE_AUTHENTICATOR,"GSSAPI");
+ break;
+ case SEC_E_NO_AUTHENTICATING_AUTHORITY:
+ s = "No authenticating authority"; break;
+ case SEC_E_TARGET_UNKNOWN:
+ s = "Destination server unknown to Kerberos service"; break;
+ default:
+ sprintf (s = tmp,"SSPI code %lx",status_value);
+ }
+ break;
+ case GSS_C_MECH_CODE: /* minor status - drop into default */
+ default:
+ return GSS_S_BAD_STATUS; /* bad status type */
+ }
+ /* return status string */
+ status_string->length = strlen (status_string->value = cpystr (s));
+ return GSS_S_COMPLETE;
+}
+
+/* GSSAPI delete security context
+ * Accepts: pointer to return minor status
+ * context to delete
+ * output context token
+ * Returns: major status, always
+ */
+
+OM_uint32 gss_delete_sec_context (OM_uint32 *minor_status,
+ gss_ctx_id_t *context_handle,
+ gss_buffer_t output_token)
+{
+ OM_uint32 major_status;
+ *minor_status = 0; /* never any minor status */
+ /* output token not supported */
+ major_status = output_token ? GSS_S_FAILURE :
+ DeleteSecurityContext (*context_handle);
+ fs_give ((void **) context_handle);
+ return major_status;
+}
+
+
+/* GSSAPI release buffer
+ * Accepts: pointer to return minor status
+ * buffer to release
+ * Returns: GSS_S_COMPLETE, always
+ */
+
+OM_uint32 gss_release_buffer (OM_uint32 *minor_status,gss_buffer_t buffer)
+{
+ *minor_status = 0; /* never any minor status */
+ fs_give (&buffer->value);
+ return GSS_S_COMPLETE;
+}
+
+
+/* GSSAPI release name
+ * Accepts: pointer to return minor status
+ * pointer to name to release
+ * Returns: GSS_S_COMPLETE, always
+ */
+
+OM_uint32 gss_release_name (OM_uint32 *minor_status,gss_name_t *input_name)
+{
+ *minor_status = 0; /* never any minor status */
+ fs_give (input_name);
+ return GSS_S_COMPLETE;
+}
+
+/* GSSAPI wrap data
+ * Accepts: pointer to return minor status
+ * context handle
+ * requested confidentiality
+ * requested quality of protection
+ * input message buffer
+ * pointer to return confidentiality state
+ * output message buffer
+ * Returns: major status, always
+ */
+
+OM_uint32 gss_wrap (OM_uint32 *minor_status,gss_ctx_id_t context_handle,
+ int conf_req_flag,gss_qop_t qop_req,
+ gss_buffer_t input_message_buffer,int *conf_state,
+ gss_buffer_t output_message_buffer)
+{
+ OM_uint32 major_status;
+ SecBuffer buf[3];
+ SecBufferDesc bufs;
+ SecPkgContext_Sizes sizes;
+ *minor_status = NIL; /* never any minor status */
+ *conf_state = conf_req_flag; /* same as requested */
+ if ((major_status = /* get trailer and padding sizes */
+ QueryContextAttributes (context_handle,SECPKG_ATTR_SIZES,&sizes)) ==
+ SEC_E_OK) {
+ /* create big enough output buffer */
+ output_message_buffer->value =
+ fs_get (sizes.cbSecurityTrailer + input_message_buffer->length +
+ sizes.cbBlockSize);
+ /* MSDN claims that for EncryptMessage() in Kerberos, you need an
+ * uninitialized SECBUFFER_STREAM_HEADER; a SECBUFFER_DATA that "contains
+ * the message to be encrypted. The message is encrypted in place,
+ * overwriting the original contents of its buffer"; an uninitialized
+ * SECBUFFER_STREAM_TRAILER, and an uninitialized SECBUFFER_EMPTY. I've
+ * never been able to get it to work that way.
+ */
+ bufs.cBuffers = 3; /* set up buffer descriptor */
+ bufs.pBuffers = buf;
+ bufs.ulVersion = SECBUFFER_VERSION;
+ buf[0].BufferType = SECBUFFER_TOKEN;
+ buf[0].pvBuffer = output_message_buffer->value;
+ buf[0].cbBuffer = sizes.cbSecurityTrailer;
+ /* I/O buffer */
+ buf[1].BufferType = SECBUFFER_DATA;
+ buf[1].pvBuffer = ((char *) buf[0].pvBuffer) + buf[0].cbBuffer;
+ buf[1].cbBuffer = input_message_buffer->length;
+ memcpy (buf[1].pvBuffer,input_message_buffer->value,buf[1].cbBuffer);
+ buf[2].BufferType = SECBUFFER_PADDING;
+ buf[2].pvBuffer = ((char *) buf[1].pvBuffer) + buf[1].cbBuffer;
+ buf[2].cbBuffer = sizes.cbBlockSize;
+ if ((major_status = EncryptMessage (context_handle,qop_req,&bufs,0)) ==
+ GSS_S_COMPLETE) {
+ /* slide data as necessary (how annoying!) */
+ unsigned long i = sizes.cbSecurityTrailer - buf[0].cbBuffer;
+ if (i) buf[1].pvBuffer =
+ memmove (((char *) buf[0].pvBuffer) + buf[0].cbBuffer,
+ buf[1].pvBuffer,buf[1].cbBuffer);
+ if (i += (input_message_buffer->length - buf[1].cbBuffer))
+ buf[1].pvBuffer = memmove (((char *)buf[1].pvBuffer) + buf[1].cbBuffer,
+ buf[2].pvBuffer,buf[2].cbBuffer);
+ output_message_buffer->length = buf[0].cbBuffer + buf[1].cbBuffer +
+ buf[2].cbBuffer;
+ }
+ else fs_give (&output_message_buffer->value);
+ }
+ return major_status; /* return status */
+}
+
+/* GSSAPI unwrap data
+ * Accepts: pointer to return minor status
+ * context handle
+ * input message buffer
+ * output message buffer
+ * pointer to return confidentiality state
+ * pointer to return quality of protection
+ * Returns: major status, always
+ */
+
+OM_uint32 gss_unwrap (OM_uint32 *minor_status,gss_ctx_id_t context_handle,
+ gss_buffer_t input_message_buffer,
+ gss_buffer_t output_message_buffer,int *conf_state,
+ gss_qop_t *qop_state)
+{
+ OM_uint32 major_status;
+ SecBuffer buf[2];
+ SecBufferDesc bufs;
+ *minor_status = NIL; /* never any minor status */
+ *conf_state = NIL; /* or confidentiality state */
+ /* MSDN implies that all that is needed for DecryptMessage() in Kerberos
+ * is a single SECBUFFER_DATA which "contains the encrypted message. The
+ * encrypted message is decrypted in place, overwriting the original
+ * contents of its buffer." I've never been able to get it to work without
+ * using a SECBUFFER_STREAM for input and an uninitialized SECBUFFER_DATA
+ * for output.
+ * It *does* overwrite the input buffer, but not at the same point; e.g.
+ * with an input pointer of 0xa140a8 and size of 53, the output ends up
+ * at 0xa140d5 and size of 4.
+ */
+ bufs.cBuffers = 2; /* set up buffer descriptor */
+ bufs.pBuffers = buf;
+ bufs.ulVersion = SECBUFFER_VERSION;
+ /* input buffer */
+ buf[0].BufferType = SECBUFFER_STREAM;
+ buf[0].pvBuffer = input_message_buffer->value;
+ buf[0].cbBuffer = input_message_buffer->length;
+ /* output buffer */
+ buf[1].BufferType = SECBUFFER_DATA;
+ buf[1].pvBuffer = NIL;
+ buf[1].cbBuffer = 0;
+ /* decrypt and copy to output buffer */
+ if ((major_status = DecryptMessage (context_handle,&bufs,0,qop_state)) ==
+ SEC_E_OK)
+ memcpy (output_message_buffer->value = fs_get (buf[1].cbBuffer),
+ buf[1].pvBuffer,output_message_buffer->length = buf[1].cbBuffer);
+ return major_status; /* return status */
+}
+
+/* From here on are server-only functions, currently unused */
+
+
+/* GSSAPI acquire credentials
+ * Accepts: pointer to return minor status
+ * desired principal
+ * desired lifetime
+ * desired mechanisms
+ * credentials usage
+ * pointer to return credentials handle
+ * pointer to return mechanisms
+ * pointer to return lifetime
+ * Returns: GSS_S_FAILURE, always
+ */
+
+OM_uint32 gss_acquire_cred (OM_uint32 *minor_status,gss_name_t desired_name,
+ OM_uint32 time_req,gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_cred_id_t *output_cred_handle,
+ gss_OID_set *actual_mechs,OM_uint32 *time_rec)
+{
+ *minor_status = 0; /* never any minor status */
+ return GSS_S_FAILURE; /* server only */
+}
+
+
+/* GSSAPI release credentials
+ * Accepts: pointer to return minor status
+ * credentials handle to free
+ * Returns: GSS_S_COMPLETE, always
+ */
+
+OM_uint32 gss_release_cred (OM_uint32 *minor_status,gss_cred_id_t *cred_handle)
+{
+ *minor_status = 0; /* never any minor status */
+ return GSS_S_FAILURE; /* server only */
+}
+
+/* GSSAPI Accept security context
+ * Accepts: pointer to return minor status
+ * context
+ * acceptor credentials
+ * input token buffer
+ * input channel bindings
+ * pointer to return source name
+ * pointer to return mechanism type
+ * buffer to return output token
+ * pointer to return flags
+ * pointer to return context lifetime
+ * pointer to return delegated credentials
+ * Returns: GSS_S_FAILURE, always
+ */
+
+OM_uint32 gss_accept_sec_context (OM_uint32 *minor_status,
+ gss_ctx_id_t *context_handle,
+ gss_cred_id_t acceptor_cred_handle,
+ gss_buffer_t input_token_buffer,
+ gss_channel_bindings_t input_chan_bindings,
+ gss_name_t *src_name,gss_OID *mech_type,
+ gss_buffer_t output_token,
+ OM_uint32 *ret_flags,OM_uint32 *time_rec,
+ gss_cred_id_t *delegated_cred_handle)
+{
+ *minor_status = 0; /* never any minor status */
+ return GSS_S_FAILURE; /* server only */
+}
+
+
+/* GSSAPI return printable name
+ * Accepts: pointer to return minor status
+ * internal name
+ * buffer to return output name
+ * output name type
+ * Returns: GSS_S_FAILURE, always
+ */
+
+OM_uint32 gss_display_name (OM_uint32 *minor_status,gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID *output_name_type)
+{
+ *minor_status = 0; /* never any minor status */
+ return GSS_S_FAILURE; /* server only */
+}
+
+/* Kerberos server valid check
+ * Returns: T if have keytab, NIL otherwise
+ */
+
+long kerberos_server_valid ()
+{
+ return NIL;
+}
+
+
+/* Kerberos check for missing or expired credentials
+ * Returns: T if should suggest running kinit, NIL otherwise
+ */
+
+long kerberos_try_kinit (OM_uint32 error)
+{
+ return NIL;
+}
+
+/* Kerberos server log in
+ * Accepts: authorization ID as user name
+ * authentication ID as Kerberos principal
+ * argument count
+ * argument vector
+ * Returns: logged in user name if logged in, NIL otherwise
+ */
+
+char *kerberos_login (char *user,char *authuser,int argc,char *argv[])
+{
+ return NIL;
+}
diff --git a/imap/src/osdep/nt/mailfile.h b/imap/src/osdep/nt/mailfile.h
new file mode 100644
index 00000000..056e66b7
--- /dev/null
+++ b/imap/src/osdep/nt/mailfile.h
@@ -0,0 +1,29 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Mail Spool file name
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 8 February 1996
+ * Last Edited: 30 August 2006
+ */
+
+#define MAILFILE "C:\\WINSMTP\\%s.MBX"
diff --git a/imap/src/osdep/nt/makefile.nt b/imap/src/osdep/nt/makefile.nt
new file mode 100644
index 00000000..0ea96e5a
--- /dev/null
+++ b/imap/src/osdep/nt/makefile.nt
@@ -0,0 +1,118 @@
+# ========================================================================
+# Copyright 1988-2007 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# ========================================================================
+
+# Program: Portable C client makefile -- NT version
+#
+# Author: Mark Crispin
+# Networks and Distributed Computing
+# Computing & Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, WA 98195
+# Internet: MRC@CAC.Washington.EDU
+#
+# Date: 11 May 1989
+# Last Edited: 23 May 2007
+
+
+EXTRAAUTHENTICATORS =
+EXTRADRIVERS =
+EXTRACFLAGS =
+AUTHENTICATORS = ext md5 pla log
+DRIVERS = imap nntp pop3 mbx mtx tenex unix
+CREATEDRIVER = mbx
+APPENDDRIVER = unix
+OSCOMPAT = /DWIN32 /D_WIN32_WINNT=0x0400
+VSCOMPAT = /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE
+CFLAGS = /MT /W3 /Ox /DCHUNKSIZE=65536 $(OSCOMPAT) $(VSCOMPAT) -nologo /I.. $(EXTRACFLAGS)
+CC = cl
+CCLIENTLIB = cclient.lib
+
+all: $(CCLIENTLIB)
+
+.c.obj:
+ $(CC) -c $(CFLAGS) $*.c
+
+osdep.h: os_nt.h
+ copy os_nt.h osdep.h
+ drivers $(EXTRADRIVERS) $(DRIVERS) dummy
+ setproto $(CREATEDRIVER) $(APPENDDRIVER)
+ echo ssl_onceonlyinit (); >> linkage.c
+ mkauths $(EXTRAAUTHENTICATORS) $(AUTHENTICATORS)
+ echo mail_versioncheck (CCLIENTVERSION); >> linkage.c
+
+ip_nt.c: ip4_nt.c
+ copy ip4_nt.c ip_nt.c
+
+mail.obj: mail.h misc.h osdep.h mail.c
+
+misc.obj: mail.h misc.h misc.c
+
+fdstring.obj: mail.h misc.h osdep.h fdstring.h fdstring.c
+
+flstring.obj: mail.h misc.h osdep.h flstring.h flstring.c
+
+netmsg.obj: mail.h misc.h netmsg.h osdep.h netmsg.c
+
+newsrc.obj: mail.h misc.h newsrc.h osdep.h newsrc.c
+
+rfc822.obj: mail.h rfc822.h misc.h rfc822.c
+
+smanager.obj: mail.h misc.h smanager.c
+
+utf8.obj: mail.h misc.h osdep.h utf8.h
+
+utf8aux.obj: mail.h misc.h osdep.h utf8.h
+
+imap4r1.obj: mail.h imap4r1.h misc.h osdep.h imap4r1.c
+
+nntp.obj: mail.h nntp.h smtp.h rfc822.h misc.h osdep.h nntp.c
+
+pop3.obj: mail.h rfc822.h misc.h osdep.h pop3.c
+
+smtp.obj: mail.h smtp.h rfc822.h misc.h osdep.h smtp.c
+
+os_nt.obj: mail.h osdep.h env_nt.h fs.h ftl.h nl.h tcp.h tcp_nt.h yunchan.h \
+ os_nt.c fs_nt.c ftl_nt.c nl_nt.c env_nt.c ssl_nt.c ssl_none.c \
+ ip_nt.c tcp_nt.c yunchan.c pmatch.c write.c \
+ mailfile.h auth_md5.c auth_pla.c auth_log.c
+
+mbxnt.obj: mail.h misc.h osdep.h mbxnt.c
+
+mtxnt.obj: mail.h misc.h osdep.h mtxnt.c
+
+tenexnt.obj: mail.h misc.h osdep.h tenexnt.c
+
+unixnt.obj: mail.h unixnt.h pseudo.h misc.h osdep.h unixnt.c
+
+dummynt.obj: mail.h dummy.h misc.h osdep.h dummynt.c
+
+pseudo.obj: pseudo.h
+
+$(CCLIENTLIB): mail.obj misc.obj fdstring.obj flstring.obj netmsg.obj \
+ newsrc.obj rfc822.obj smanager.obj utf8.obj utf8aux.obj \
+ imap4r1.obj nntp.obj pop3.obj smtp.obj os_nt.obj \
+ mbxnt.obj mtxnt.obj tenexnt.obj unixnt.obj dummynt.obj pseudo.obj
+ if exist $(CCLIENTLIB) del $(CCLIENTLIB)
+ LIB /NOLOGO /OUT:cclient.lib \
+ mail.obj misc.obj fdstring.obj flstring.obj netmsg.obj \
+ newsrc.obj rfc822.obj smanager.obj utf8.obj utf8aux.obj \
+ imap4r1.obj nntp.obj pop3.obj smtp.obj os_nt.obj \
+ mbxnt.obj mtxnt.obj tenexnt.obj unixnt.obj dummynt.obj pseudo.obj
+
+clean:
+ del *.lib *.obj linkage.* osdep.* ip_nt.c auths.c *.exe *.exp || rem
+
+# A monument to a hack of long ago and far away...
+
+love:
+ @echo not war?
diff --git a/imap/src/osdep/nt/makefile.ntk b/imap/src/osdep/nt/makefile.ntk
new file mode 100644
index 00000000..e383e0f8
--- /dev/null
+++ b/imap/src/osdep/nt/makefile.ntk
@@ -0,0 +1,118 @@
+# ========================================================================
+# Copyright 1988-2007 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# ========================================================================
+
+# Program: Portable C client makefile -- NT version + Kerberos
+#
+# Author: Mark Crispin
+# Networks and Distributed Computing
+# Computing & Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, WA 98195
+# Internet: MRC@CAC.Washington.EDU
+#
+# Date: 11 May 1989
+# Last Edited: 23 May 2007
+
+
+EXTRAAUTHENTICATORS =
+EXTRADRIVERS =
+EXTRACFLAGS =
+AUTHENTICATORS = ext gss md5 pla log
+DRIVERS = imap nntp pop3 mbx mtx tenex unix
+CREATEDRIVER = mbx
+APPENDDRIVER = unix
+OSCOMPAT = /DWIN32 /D_WIN32_WINNT=0x0400 /I\k5\include
+VSCOMPAT = /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE
+CFLAGS = /MT /W3 /Ox /DCHUNKSIZE=65536 $(OSCOMPAT) $(VSCOMPAT) -nologo /I.. $(EXTRACFLAGS)
+CC = cl
+CCLIENTLIB = cclient.lib
+
+all: $(CCLIENTLIB)
+
+.c.obj:
+ $(CC) -c $(CFLAGS) $*.c
+
+osdep.h: os_nt.h
+ copy os_nt.h osdep.h
+ drivers $(EXTRADRIVERS) $(DRIVERS) dummy
+ setproto $(CREATEDRIVER) $(APPENDDRIVER)
+ echo ssl_onceonlyinit (); >> linkage.c
+ mkauths $(EXTRAAUTHENTICATORS) $(AUTHENTICATORS)
+ echo mail_versioncheck (CCLIENTVERSION); >> linkage.c
+
+ip_nt.c: ip4_nt.c
+ copy ip4_nt.c ip_nt.c
+
+mail.obj: mail.h misc.h osdep.h mail.c
+
+misc.obj: mail.h misc.h misc.c
+
+fdstring.obj: mail.h misc.h osdep.h fdstring.h fdstring.c
+
+flstring.obj: mail.h misc.h osdep.h flstring.h flstring.c
+
+netmsg.obj: mail.h misc.h netmsg.h osdep.h netmsg.c
+
+newsrc.obj: mail.h misc.h newsrc.h osdep.h newsrc.c
+
+rfc822.obj: mail.h rfc822.h misc.h rfc822.c
+
+smanager.obj: mail.h misc.h smanager.c
+
+utf8.obj: mail.h misc.h osdep.h utf8.h
+
+utf8aux.obj: mail.h misc.h osdep.h utf8.h
+
+imap4r1.obj: mail.h imap4r1.h misc.h osdep.h imap4r1.c
+
+nntp.obj: mail.h nntp.h smtp.h rfc822.h misc.h osdep.h nntp.c
+
+pop3.obj: mail.h rfc822.h misc.h osdep.h pop3.c
+
+smtp.obj: mail.h smtp.h rfc822.h misc.h osdep.h smtp.c
+
+os_ntk.obj: mail.h osdep.h env_nt.h fs.h ftl.h nl.h tcp.h tcp_nt.h yunchan.h \
+ os_ntk.c fs_nt.c ftl_nt.c nl_nt.c env_nt.c ssl_nt.c ssl_none.c \
+ ip_nt.c tcp_nt.c yunchan.c pmatch.c write.c \
+ mailfile.h auth_gss.c auth_md5.c auth_pla.c auth_log.c kerb_mit.c
+
+mbxnt.obj: mail.h misc.h osdep.h mbxnt.c
+
+mtxnt.obj: mail.h misc.h osdep.h mtxnt.c
+
+tenexnt.obj: mail.h misc.h osdep.h tenexnt.c
+
+unixnt.obj: mail.h unixnt.h pseudo.h misc.h osdep.h unixnt.c
+
+dummynt.obj: mail.h dummy.h misc.h osdep.h dummynt.c
+
+pseudo.obj: pseudo.h
+
+$(CCLIENTLIB): mail.obj misc.obj fdstring.obj flstring.obj netmsg.obj \
+ newsrc.obj rfc822.obj smanager.obj utf8.obj utf8aux.obj \
+ imap4r1.obj nntp.obj pop3.obj smtp.obj os_ntk.obj \
+ mbxnt.obj mtxnt.obj tenexnt.obj unixnt.obj dummynt.obj pseudo.obj
+ if exist $(CCLIENTLIB) del $(CCLIENTLIB)
+ LIB /NOLOGO /OUT:cclient.lib \
+ mail.obj misc.obj fdstring.obj flstring.obj netmsg.obj \
+ newsrc.obj rfc822.obj smanager.obj utf8.obj utf8aux.obj \
+ imap4r1.obj nntp.obj pop3.obj smtp.obj os_ntk.obj \
+ mbxnt.obj mtxnt.obj tenexnt.obj unixnt.obj dummynt.obj pseudo.obj
+
+clean:
+ del *.lib *.obj linkage.* osdep.* ip_nt.c auths.c *.exe *.exp || rem
+
+# A monument to a hack of long ago and far away...
+
+love:
+ @echo not war?
diff --git a/imap/src/osdep/nt/makefile.w2k b/imap/src/osdep/nt/makefile.w2k
new file mode 100644
index 00000000..a3d62ad6
--- /dev/null
+++ b/imap/src/osdep/nt/makefile.w2k
@@ -0,0 +1,119 @@
+# ========================================================================
+# Copyright 1988-2007 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# ========================================================================
+
+# Program: Portable C client makefile -- Windows 2000/XP version
+#
+# Author: Mark Crispin
+# Networks and Distributed Computing
+# Computing & Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, WA 98195
+# Internet: MRC@CAC.Washington.EDU
+#
+# Date: 11 May 1989
+# Last Edited: 23 May 2007
+
+
+IP=6
+EXTRAAUTHENTICATORS =
+EXTRADRIVERS =
+EXTRACFLAGS =
+AUTHENTICATORS = ext gss md5 pla log
+DRIVERS = imap nntp pop3 mbx mtx tenex unix
+CREATEDRIVER = mbx
+APPENDDRIVER = unix
+OSCOMPAT = /DWIN32
+VSCOMPAT = /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE
+CFLAGS = /MT /W3 /Ox /DCHUNKSIZE=65536 $(OSCOMPAT) $(VSCOMPAT) -nologo /I.. $(EXTRACFLAGS)
+CC = cl
+CCLIENTLIB = cclient.lib
+
+all: $(CCLIENTLIB)
+
+.c.obj:
+ $(CC) -c $(CFLAGS) $*.c
+
+osdep.h: os_nt.h
+ copy os_nt.h osdep.h
+ drivers $(EXTRADRIVERS) $(DRIVERS) dummy
+ setproto $(CREATEDRIVER) $(APPENDDRIVER)
+ echo ssl_onceonlyinit (); >> linkage.c
+ mkauths $(EXTRAAUTHENTICATORS) $(AUTHENTICATORS)
+ echo mail_versioncheck (CCLIENTVERSION); >> linkage.c
+
+ip_nt.c: ip$(IP)_nt.c
+ copy ip$(IP)_nt.c ip_nt.c
+
+mail.obj: mail.h misc.h osdep.h mail.c
+
+misc.obj: mail.h misc.h misc.c
+
+fdstring.obj: mail.h misc.h osdep.h fdstring.h fdstring.c
+
+flstring.obj: mail.h misc.h osdep.h flstring.h flstring.c
+
+netmsg.obj: mail.h misc.h netmsg.h osdep.h netmsg.c
+
+newsrc.obj: mail.h misc.h newsrc.h osdep.h newsrc.c
+
+rfc822.obj: mail.h rfc822.h misc.h rfc822.c
+
+smanager.obj: mail.h misc.h smanager.c
+
+utf8.obj: mail.h misc.h osdep.h utf8.h
+
+utf8aux.obj: mail.h misc.h osdep.h utf8.h
+
+imap4r1.obj: mail.h imap4r1.h misc.h osdep.h imap4r1.c
+
+nntp.obj: mail.h nntp.h smtp.h rfc822.h misc.h osdep.h nntp.c
+
+pop3.obj: mail.h rfc822.h misc.h osdep.h pop3.c
+
+smtp.obj: mail.h smtp.h rfc822.h misc.h osdep.h smtp.c
+
+os_w2k.obj: mail.h osdep.h env_nt.h fs.h ftl.h nl.h tcp.h tcp_nt.h yunchan.h \
+ os_w2k.c fs_nt.c ftl_nt.c nl_nt.c env_nt.c ssl_w2k.c ssl_none.c \
+ ip_nt.c tcp_nt.c yunchan.c pmatch.c write.c \
+ mailfile.h auth_gss.c auth_md5.c auth_pla.c auth_log.c kerb_w2k.c
+
+mbxnt.obj: mail.h misc.h osdep.h mbxnt.c
+
+mtxnt.obj: mail.h misc.h osdep.h mtxnt.c
+
+tenexnt.obj: mail.h misc.h osdep.h tenexnt.c
+
+unixnt.obj: mail.h unixnt.h pseudo.h misc.h osdep.h unixnt.c
+
+dummynt.obj: mail.h dummy.h misc.h osdep.h dummynt.c
+
+pseudo.obj: pseudo.h
+
+$(CCLIENTLIB): mail.obj misc.obj fdstring.obj flstring.obj netmsg.obj \
+ newsrc.obj rfc822.obj smanager.obj utf8.obj utf8aux.obj \
+ imap4r1.obj nntp.obj pop3.obj smtp.obj os_w2k.obj \
+ mbxnt.obj mtxnt.obj tenexnt.obj unixnt.obj dummynt.obj pseudo.obj
+ if exist $(CCLIENTLIB) del $(CCLIENTLIB)
+ LIB /NOLOGO /OUT:cclient.lib \
+ mail.obj misc.obj fdstring.obj flstring.obj netmsg.obj \
+ newsrc.obj rfc822.obj smanager.obj utf8.obj utf8aux.obj \
+ imap4r1.obj nntp.obj pop3.obj smtp.obj os_w2k.obj \
+ mbxnt.obj mtxnt.obj tenexnt.obj unixnt.obj dummynt.obj pseudo.obj
+
+clean:
+ del *.lib *.obj linkage.* osdep.* ip_nt.c auths.c *.exe *.exp || rem
+
+# A monument to a hack of long ago and far away...
+
+love:
+ @echo not war?
diff --git a/imap/src/osdep/nt/mbxnt.c b/imap/src/osdep/nt/mbxnt.c
new file mode 100644
index 00000000..bb3d4cc0
--- /dev/null
+++ b/imap/src/osdep/nt/mbxnt.c
@@ -0,0 +1,1694 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MBX mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 3 October 1995
+ * Last Edited: 28 September 2007
+ */
+
+
+/* FILE TIME SEMANTICS
+ *
+ * The atime is the last read time of the file.
+ * The mtime is the last flags update time of the file.
+ * The ctime is the last write time of the file.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/utime.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+
+/* Build parameters */
+
+#define HDRSIZE 2048
+
+/* MBX I/O stream local data */
+
+typedef struct mbx_local {
+ unsigned int flagcheck: 1; /* if ping should sweep for flags */
+ unsigned int expok: 1; /* if expunging OK in ping */
+ unsigned int expunged : 1; /* if one or more expunged messages */
+ int fd; /* file descriptor for I/O */
+ int ld; /* lock file descriptor */
+ int ffuserflag; /* first free user flag */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* last snarf time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ char lock[MAILTMPLEN]; /* buffer to write lock name */
+} MBXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MBXLOCAL *) stream->local)
+
+/* Function prototypes */
+
+DRIVER *mbx_valid (char *name);
+int mbx_isvalid (MAILSTREAM **stream,char *name,char *file,int *ld,char *lock,
+ long flags);
+void *mbx_parameters (long function,void *value);
+void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void mbx_list (MAILSTREAM *stream,char *ref,char *pat);
+void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mbx_create (MAILSTREAM *stream,char *mailbox);
+long mbx_delete (MAILSTREAM *stream,char *mailbox);
+long mbx_rename (MAILSTREAM *stream,char *old,char *newname);
+long mbx_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *mbx_open (MAILSTREAM *stream);
+void mbx_close (MAILSTREAM *stream,long options);
+void mbx_abort (MAILSTREAM *stream);
+void mbx_flags (MAILSTREAM *stream,char *sequence,long flags);
+char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags);
+long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long mbx_ping (MAILSTREAM *stream);
+void mbx_check (MAILSTREAM *stream);
+long mbx_expunge (MAILSTREAM *stream,char *sequence,long options);
+void mbx_snarf (MAILSTREAM *stream);
+long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+long mbx_parse (MAILSTREAM *stream);
+MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok);
+unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
+void mbx_update_header (MAILSTREAM *stream);
+void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags);
+unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size,char **hdr);
+unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed,
+ long flags);
+long mbx_flaglock (MAILSTREAM *stream);
+
+/* MBX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mbxdriver = {
+ "mbx", /* driver name */
+ DR_LOCAL|DR_MAIL|DR_CRLF|DR_LOCKING,
+ /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ mbx_valid, /* mailbox is valid for us */
+ mbx_parameters, /* manipulate parameters */
+ mbx_scan, /* scan mailboxes */
+ mbx_list, /* list mailboxes */
+ mbx_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ mbx_create, /* create mailbox */
+ mbx_delete, /* delete mailbox */
+ mbx_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ mbx_open, /* open mailbox */
+ mbx_close, /* close mailbox */
+ mbx_flags, /* fetch message "fast" attributes */
+ mbx_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mbx_header, /* fetch message header */
+ mbx_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ mbx_flag, /* modify flags */
+ mbx_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mbx_ping, /* ping mailbox to see if still alive */
+ mbx_check, /* check for new messages */
+ mbx_expunge, /* expunge deleted messages */
+ mbx_copy, /* copy messages to another mailbox */
+ mbx_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mbxproto = {&mbxdriver};
+
+/* MBX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mbx_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ int fd = mbx_isvalid (NIL,name,tmp,NIL,NIL,NIL);
+ if (fd < 0) return NIL;
+ close (fd); /* don't need the fd now */
+ return &mbxdriver;
+}
+
+
+/* MBX mail test for valid mailbox
+ * Accepts: returned stream with valid mailbox keywords
+ * mailbox name
+ * buffer to write file name
+ * returned lock fd
+ * returned lock name
+ * RW flags or NIL for readonly
+ * Returns: file descriptor if valid, NIL otherwise
+ */
+
+#define MBXISVALIDNOUID 0x1 /* RW, don't do UID action */
+#define MBXISVALIDUID 0x2 /* RW, do UID action */
+
+int mbx_isvalid (MAILSTREAM **stream,char *name,char *file,int *ld,char *lock,
+ long flags)
+{
+ int fd,upd;
+ int ret = -1;
+ unsigned long i;
+ long j,k;
+ off_t pos;
+ char c,*s,*t,hdr[HDRSIZE];
+ struct stat sbuf;
+ struct utimbuf times;
+ int error = EINVAL; /* assume invalid argument */
+ if (ld) *ld = -1; /* initially no lock */
+ /* if file, get its status */
+ if ((s = dummy_file (file,name)) && !stat (s,&sbuf) &&
+ ((sbuf.st_mode & S_IFMT) == S_IFREG) &&
+ ((fd = open (file,(flags ? O_RDWR : O_RDONLY)|O_BINARY,NIL)) >= 0)) {
+ error = -1; /* assume bogus format */
+ if (((((j = read (fd,hdr,HDRSIZE)) == HDRSIZE) && (hdr[0] == '*')) ||
+ /* locked, set byte 0 to "*", read rest */
+ ((j < 0) && (lseek (fd,1,L_SET) == 1) &&
+ (read (fd,hdr+1,HDRSIZE-1) == (HDRSIZE-1)) && (hdr[0] = '*'))) &&
+ (hdr[1] == 'm') && (hdr[2] == 'b') && (hdr[3] == 'x') &&
+ (hdr[4] == '*') && (hdr[5] == '\015') && (hdr[6] == '\012') &&
+ isxdigit (hdr[7]) && isxdigit (hdr[8]) && isxdigit (hdr[9]) &&
+ isxdigit (hdr[10]) && isxdigit (hdr[11]) && isxdigit (hdr[12]) &&
+ isxdigit (hdr[13]) && isxdigit (hdr[14]) && isxdigit (c = hdr[15]) &&
+ isxdigit (hdr[16]) && isxdigit (hdr[17]) && isxdigit (hdr[18]) &&
+ isxdigit (hdr[19]) && isxdigit (hdr[20]) && isxdigit (hdr[21]) &&
+ isxdigit (hdr[22]) && (hdr[23] == '\015') && (hdr[24] == '\012')) {
+ ret = fd; /* mbx format */
+
+ if (stream) { /* lock if making a mini-stream */
+ if (flock (fd,LOCK_SH) ||
+ (flags && ((*ld = lockname (lock,file,LOCK_EX)) < 0))) ret = -1;
+ /* reread data now that locked */
+ else if (lseek (fd,0,L_SET) ||
+ (read (fd,hdr+1,HDRSIZE-1) != (HDRSIZE-1))) ret = -1;
+ else {
+ *stream = (MAILSTREAM *) memset (fs_get (sizeof (MAILSTREAM)),0,
+ sizeof (MAILSTREAM));
+ hdr[15] = '\0'; /* tie off UIDVALIDITY */
+ (*stream)->uid_validity = strtoul (hdr+7,NIL,16);
+ hdr[15] = c; /* now get UIDLAST */
+ (*stream)->uid_last = strtoul (hdr+15,NIL,16);
+ /* parse user flags */
+ for (i = 0, s = hdr + 25;
+ (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s);
+ i++, s = t + 2) {
+ *t = '\0'; /* tie off flag */
+ if (strlen (s) <= MAXUSERFLAG)
+ (*stream)->user_flags[i] = cpystr (s);
+ }
+ /* make sure have true UIDLAST */
+ if (flags & MBXISVALIDUID) {
+ for (upd = NIL,pos = 2048, k = 0; pos < sbuf.st_size;
+ pos += (j + k)) {
+ /* read header for this message */
+ lseek (fd,pos,L_SET);
+ if ((j = read (fd,hdr,64)) >= 0) {
+ hdr[j] = '\0';
+ if ((s = strchr (hdr,'\015')) && (s[1] == '\012')) {
+ *s = '\0';
+ k = s + 2 - hdr;
+ if ((s = strchr (hdr,',')) && (j = strtol (s+1,&s,10)) &&
+ (*s == ';') && (s = strchr (s+1,'-'))) {
+ /* get UID if there is any */
+ i = strtoul (++s,&t,16);
+ if (!*t && (t == (s + 8)) && (i <= (*stream)->uid_last)) {
+ if (!i) {
+ lseek (fd,pos + s - hdr,L_SET);
+ sprintf (hdr,"%08lx",++(*stream)->uid_last);
+ write (fd,hdr,8);
+ upd = T;
+ }
+ continue;
+ }
+ }
+ }
+ ret = -1; /* error, give up */
+ *stream = mail_close (*stream);
+ pos = sbuf.st_size + 1;
+ j = k = 0;
+ }
+ }
+
+ if (upd) { /* need to update hdr with new UIDLAST? */
+ lseek (fd,15,L_SET);
+ sprintf (hdr,"%08lx",(*stream)->uid_last);
+ write (fd,hdr,8);
+ }
+ }
+ }
+ }
+ }
+ if (ret != fd) close (fd); /* close the file */
+ else lseek (fd,0,L_SET); /* else rewind to start */
+ /* \Marked status? */
+ if (sbuf.st_ctime > sbuf.st_atime) {
+ /* preserve atime and mtime */
+ times.actime = sbuf.st_atime;
+ times.modtime = sbuf.st_mtime;
+ utime (file,&times); /* set the times */
+ }
+ }
+ /* in case INBOX but not mbx format */
+ else if (((error = errno) == ENOENT) && !compare_cstring (name,"INBOX"))
+ error = -1;
+ if ((ret < 0) && ld && (*ld >= 0)) {
+ unlockfd (*ld,lock);
+ *ld = -1;
+ }
+ errno = error; /* return as last error */
+ return ret; /* return what we should */
+}
+
+/* MBX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mbx_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_ONETIMEEXPUNGEATPING:
+ if (value) ((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T;
+ case GET_ONETIMEEXPUNGEATPING:
+ if (value) ret = (void *)
+ (((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL);
+ break;
+ }
+ return ret;
+}
+
+
+/* MBX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MBX mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mbx_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MBX mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MBX mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,mbx[MAILTMPLEN],tmp[HDRSIZE];
+ long ret = NIL;
+ int i,fd;
+ if (!(s = dummy_file (mbx,mailbox))) {
+ sprintf (mbx,"Can't create %.80s: invalid name",mailbox);
+ mm_log (mbx,ERROR);
+ }
+ /* create underlying file */
+ else if (dummy_create (stream,s)) {
+ /* done if made directory */
+ if ((s = strrchr (s,'\\')) && !s[1]) return T;
+ if ((fd = open (mbx,O_WRONLY|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
+ mm_log (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else {
+ memset (tmp,'\0',HDRSIZE);/* initialize header */
+ sprintf (s = tmp,"*mbx*\015\012%08lx00000000\015\012",
+ (unsigned long) time (0));
+ for (i = 0; i < NUSERFLAGS; ++i)
+ sprintf (s += strlen (s),"%s\015\012",
+ (stream && stream->user_flags[i]) ? stream->user_flags[i] :
+ "");
+ if (write (fd,tmp,HDRSIZE) != HDRSIZE) {
+ sprintf (tmp,"Can't initialize mailbox node %.80s: %s",
+ mbx,strerror (errno));
+ mm_log (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else ret = T; /* success */
+ close (fd); /* close file */
+ }
+ }
+ return ret;
+}
+
+
+/* MBX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return mbx_rename (stream,mailbox,NIL);
+}
+
+/* MBX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = LONGT;
+ char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ if (!dummy_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'\\')) && !s[1])))) {
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ else if ((fd = open (file,O_RDWR|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get parse/append permission */
+ if ((ld = lockname (lock,file,LOCK_EX)) < 0) {
+ mm_log ("Unable to lock rename mailbox",ERROR);
+ return NIL;
+ }
+ /* lock out other users */
+ if (flock (fd,LOCK_EX|LOCK_NB)) {
+ close (fd); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ mm_log (tmp,ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return NIL;
+ }
+
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if ((s = strrchr (tmp,'\\')) && (s != tmp) &&
+ ((tmp[1] != ':') || (s != tmp + 2))) {
+ c = s[1]; /* remember character after delimiter */
+ *s = s[1] = '\0'; /* tie off name at delimiter */
+ /* name doesn't exist, create it */
+ if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
+ *s = '\\'; /* restore delimiter */
+ if (!dummy_create (stream,tmp)) ret = NIL;
+ }
+ else *s = '\\'; /* restore delimiter */
+ s[1] = c; /* restore character after delimiter */
+ }
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* pacify NTFS */
+ /* rename the file */
+ if (ret && rename (file,tmp)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ else {
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* pacify NTFS */
+ if (unlink (file)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* recreate file if renamed INBOX */
+ if (ret && !compare_cstring (old,"INBOX")) mbx_create (NIL,"INBOX");
+ return ret; /* return success */
+}
+
+/* MBX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mbx_open (MAILSTREAM *stream)
+{
+ int fd,ld;
+ short silent;
+ char tmp[MAILTMPLEN];
+ if (!stream) return &mbxproto;/* return prototype for OP_PROTOTYPE call */
+ if (stream->local) fatal ("mbx recycle stream");
+ /* canonicalize the mailbox name */
+ if (!dummy_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ }
+ if (stream->rdonly ||
+ (fd = open (tmp,O_RDWR|O_BINARY,NIL)) < 0) {
+ if ((fd = open (tmp,O_RDONLY|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ else if (!stream->rdonly) { /* got it, but readonly */
+ mm_log ("Can't get write access to mailbox, access is readonly",WARN);
+ stream->rdonly = T;
+ }
+ }
+
+ stream->local = memset (fs_get (sizeof (MBXLOCAL)),NIL,sizeof (MBXLOCAL));
+ LOCAL->fd = fd; /* bind the file */
+ LOCAL->ld = -1; /* no flaglock */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ /* get parse/append permission */
+ if ((ld = lockname (tmp,stream->mailbox,LOCK_EX)) < 0) {
+ mm_log ("Unable to lock open mailbox",ERROR);
+ return NIL;
+ }
+ flock (LOCAL->fd,LOCK_SH); /* lock the file */
+ unlockfd (ld,tmp); /* release shared parse permission */
+ LOCAL->filesize = HDRSIZE; /* initialize parsed file size */
+ LOCAL->filetime = 0; /* time not set up yet */
+ LOCAL->expok = LOCAL->flagcheck = NIL;
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ silent = stream->silent; /* defer events */
+ stream->silent = T;
+ if (mbx_ping (stream) && !stream->nmsgs)
+ mm_log ("Mailbox is empty",(long) NIL);
+ stream->silent = silent; /* now notify upper level */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,stream->recent);
+ if (!LOCAL) return NIL; /* failure if stream died */
+ stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
+ stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ stream->kwd_create = (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ?
+ NIL : T; /* can we create new user flags? */
+ return stream; /* return stream to caller */
+}
+
+/* MBX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mbx_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ /* do an expunge if requested */
+ if (options & CL_EXPUNGE) mbx_expunge (stream,NIL,NIL);
+ else { /* otherwise do a checkpoint to purge */
+ LOCAL->expok = T; /* possible expunged messages */
+ mbx_ping (stream);
+ }
+ stream->silent = silent; /* restore previous status */
+ mbx_abort (stream);
+ }
+}
+
+
+/* MBX mail abort stream
+ * Accepts: MAIL stream
+ */
+
+void mbx_abort (MAILSTREAM *stream)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ flock (LOCAL->fd,LOCK_UN); /* unlock local file */
+ close (LOCAL->fd); /* close the local file */
+ /* free local text buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+
+/* MBX mail fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ * Sniffs at file to see if some other process changed the flags
+ */
+
+void mbx_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned long i;
+ if (mbx_ping (stream) && /* ping mailbox, get new status for messages */
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence && !elt->valid)
+ mbx_elt (stream,i,NIL);
+}
+
+/* MBX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ unsigned long i;
+ char *s;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get header position, possibly header */
+ i = mbx_hdrpos (stream,msgno,length,&s);
+ if (!s) { /* mbx_hdrpos() returned header? */
+ lseek (LOCAL->fd,i,L_SET); /* no, get to header position */
+ /* is buffer big enough? */
+ if (*length > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = *length) + 1);
+ }
+ /* slurp the data */
+ read (LOCAL->fd,s = LOCAL->buf,*length);
+ }
+ s[*length] = '\0'; /* tie off string */
+ return s;
+}
+
+/* MBX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ FDDATA d;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ /* get message status */
+ elt = mbx_elt (stream,msgno,NIL);
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen && mbx_flaglock (stream)) {
+ elt->seen = T; /* mark message as seen */
+ /* recalculate status */
+ mbx_update_status (stream,msgno,NIL);
+ mm_flags (stream,msgno);
+ /* update flags */
+ mbx_flag (stream,NIL,NIL,NIL);
+ }
+ if (!LOCAL) return NIL; /* mbx_flaglock() could have aborted */
+ /* find header position */
+ i = mbx_hdrpos (stream,msgno,&j,NIL);
+ d.fd = LOCAL->fd; /* set up file descriptor */
+ d.pos = i + j;
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE;
+ INIT (bs,fd_string,&d,elt->rfc822_size - j);
+ return LONGT; /* success */
+}
+
+/* MBX mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ * Unlocks flag lock
+ */
+
+void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ struct utimbuf times;
+ struct stat sbuf;
+ /* make sure the update takes */
+ if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld >= 0)) {
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ /* update header */
+ if ((LOCAL->ffuserflag < NUSERFLAGS) &&
+ stream->user_flags[LOCAL->ffuserflag]) mbx_update_header (stream);
+ times.actime = time (0); /* make sure read comes after all that */
+ utime (stream->mailbox,&times);
+ }
+ if (LOCAL->ld >= 0) { /* unlock now */
+ unlockfd (LOCAL->ld,LOCAL->lock);
+ LOCAL->ld = -1;
+ }
+}
+
+
+/* MBX mail per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ if (mbx_flaglock (stream)) mbx_update_status (stream,elt->msgno,NIL);
+}
+
+/* MBX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long mbx_ping (MAILSTREAM *stream)
+{
+ unsigned long i,pos;
+ long ret = NIL;
+ int ld;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ struct stat sbuf;
+ if (stream && LOCAL) { /* only if stream already open */
+ ret = LONGT; /* assume OK */
+ fstat (LOCAL->fd,&sbuf); /* get current file poop */
+ /* allow expunge if permitted at ping */
+ if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T;
+ /* if external modification */
+ if (LOCAL->filetime && (LOCAL->filetime < sbuf.st_mtime))
+ LOCAL->flagcheck = T; /* upgrade to flag checking */
+ /* new mail or flagcheck handling needed? */
+ if (((sbuf.st_size - LOCAL->filesize) || LOCAL->flagcheck ||
+ !stream->nmsgs) &&
+ ((ld = lockname (lock,stream->mailbox,LOCK_EX)) >= 0)) {
+ if (!LOCAL->flagcheck) ret = mbx_parse (stream);
+ /* sweep mailbox for changed message status */
+ else if (ret = mbx_parse (stream)) {
+ unsigned long recent = 0;
+ LOCAL->filetime = sbuf.st_mtime;
+ for (i = 1; i <= stream->nmsgs; )
+ if (elt = mbx_elt (stream,i,LOCAL->expok)) {
+ if (elt->recent) ++recent;
+ ++i;
+ }
+ mail_recent (stream,recent);
+ LOCAL->flagcheck = NIL; /* got all the updates */
+ }
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ if (ret) { /* must still be alive */
+ if (!LOCAL->expunged) /* look for holes if none known yet */
+ for (i = 1, pos = HDRSIZE;
+ !LOCAL->expunged && (i <= stream->nmsgs);
+ i++, pos += elt->private.special.text.size + elt->rfc822_size)
+ if ((elt = mail_elt (stream,i))->private.special.offset != pos)
+ LOCAL->expunged = T;/* found a hole */
+ /* burp any holes */
+ if (LOCAL->expunged && !stream->rdonly) {
+ if (mbx_rewrite (stream,&i,NIL)) fatal ("expunge on check");
+ if (i) { /* any space reclaimed? */
+ LOCAL->expunged = NIL;/* no more pending expunge */
+ sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",i);
+ mm_log (LOCAL->buf,(long) NIL);
+ }
+ }
+ LOCAL->expok = NIL; /* no more expok */
+ }
+ }
+ return ret; /* return result of the parse */
+}
+
+/* MBX mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void mbx_check (MAILSTREAM *stream)
+{
+ if (LOCAL) LOCAL->expok = T; /* mark that a check is desired */
+ if (mbx_ping (stream)) mm_log ("Check completed",(long) NIL);
+}
+
+
+/* MBX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T if success, NIL if failure
+ */
+
+long mbx_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ unsigned long nexp,reclaimed;
+ if (ret = sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) {
+ if (!mbx_ping (stream)); /* do nothing if stream dead */
+ else if (stream->rdonly) /* won't do on readonly files! */
+ mm_log ("Expunge ignored on readonly mailbox",WARN);
+ /* if expunged any messages */
+ else if (nexp = mbx_rewrite (stream,&reclaimed,sequence ? -1 : 1)) {
+ sprintf (LOCAL->buf,"Expunged %lu messages",nexp);
+ mm_log (LOCAL->buf,(long) NIL);
+ }
+ else if (reclaimed) { /* or if any prior expunged space reclaimed */
+ sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed);
+ mm_log (LOCAL->buf,(long) NIL);
+ }
+ else mm_log ("No messages deleted, so no update needed",(long) NIL);
+ }
+ return ret;
+}
+
+/* MBX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ struct utimbuf times;
+ MESSAGECACHE *elt;
+ unsigned long i,j,k,m;
+ long ret = LONGT;
+ int fd,ld;
+ char *s,*t,file[MAILTMPLEN],lock[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ MAILSTREAM *dstream = NIL;
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* make sure valid mailbox */
+ if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
+ cu ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid MBX-format mailbox name: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a MBX-format mailbox: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ /* got file? */
+ if ((fd = open (dummy_file (file,mailbox),O_RDWR|O_CREAT|O_BINARY,
+ S_IREAD|S_IWRITE)) < 0) {
+ sprintf (LOCAL->buf,"Unable to open copy mailbox: %s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ mm_critical (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
+
+ /* for each requested message */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.special.text.size,L_SET);
+ mail_date(LOCAL->buf,elt);/* build target header */
+ /* get target keyword mask */
+ for (j = elt->user_flags, k = 0; j; )
+ if (s = stream->user_flags[find_rightmost_bit (&j)])
+ for (m = 0; (m < NUSERFLAGS) && (t = dstream->user_flags[m]); m++)
+ if (!compare_cstring (s,t) && (k |= 1 << m)) break;
+ sprintf (LOCAL->buf+strlen(LOCAL->buf),",%lu;%08lx%04x-%08lx\015\012",
+ elt->rfc822_size,k,(unsigned)
+ ((fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)),cu ? ++dstream->uid_last : 0);
+ /* write target header */
+ if (ret = (write (fd,LOCAL->buf,strlen (LOCAL->buf)) > 0)) {
+ for (k = elt->rfc822_size; ret && (j = min (k,LOCAL->buflen)); k -= j){
+ read (LOCAL->fd,LOCAL->buf,j);
+ ret = write (fd,LOCAL->buf,j) >= 0;
+ }
+ if (cu) { /* need to pass back new UID? */
+ mail_append_set (source,mail_uid (stream,i));
+ mail_append_set (dest,dstream->uid_last);
+ }
+ }
+ }
+
+ /* make sure all the updates take */
+ if (!(ret && (ret = !fsync (fd)))) {
+ sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ }
+ if (cu && ret) { /* return sets if doing COPYUID */
+ (*cu) (stream,mailbox,dstream->uid_validity,source,dest);
+ lseek (fd,15,L_SET); /* update UIDLAST */
+ sprintf (LOCAL->buf,"%08lx",dstream->uid_last);
+ write (fd,LOCAL->buf,8);
+ }
+ else { /* flush any sets we may have built */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ /* set atime to now-1 if successful copy */
+ if (ret) times.actime = time (0) - 1;
+ /* else preserved \Marked status */
+ else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
+ sbuf.st_atime : time (0);
+ times.modtime = sbuf.st_mtime;/* preserve mtime */
+ utime (file,&times); /* set the times */
+ close (fd); /* close the file */
+ mm_nocritical (stream); /* release critical */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE) && mbx_flaglock (stream)) {
+ for (i = 1; i <= stream->nmsgs; i++) if (mail_elt (stream,i)->sequence) {
+ /* mark message deleted */
+ mbx_elt (stream,i,NIL)->deleted = T;
+ /* recalculate status */
+ mbx_update_status (stream,i,NIL);
+ }
+ /* update flags */
+ mbx_flag (stream,NIL,NIL,NIL);
+ }
+ if (dstream != stream) mail_close (dstream);
+ return ret;
+}
+
+/* MBX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd,ld;
+ char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ struct utimbuf times;
+ FILE *df;
+ MESSAGECACHE elt;
+ long f;
+ unsigned long i,uf;
+ STRING *message;
+ long ret = NIL;
+ MAILSTREAM *dstream = NIL;
+ appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ /* make sure valid mailbox */
+ /* make sure valid mailbox */
+ if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
+ au ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* can create INBOX here */
+ mbx_create (dstream = stream ? stream : &mbxproto,"INBOX");
+ if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
+ au ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid MBX-format mailbox name: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MBX-format mailbox: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+
+ /* get first message */
+ if (!(*af) (dstream,data,&flags,&date,&message)) close (fd);
+ else if (!(df = fdopen (fd,"r+b"))) {
+ MM_LOG ("Unable to reopen append mailbox",ERROR);
+ close (fd);
+ }
+ else {
+ mm_critical (dstream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ fseek (df,sbuf.st_size,SEEK_SET);
+ errno = 0;
+ for (ret = LONGT; ret && message; ) {
+ if (!SIZE (message)) { /* guard against zero-length */
+ mm_log ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ f = mail_parse_flags (dstream,flags,&uf);
+ if (date) { /* parse date if given */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ mm_log (tmp,ERROR);
+ ret = NIL; /* mark failure */
+ break;
+ }
+ mail_date (tmp,&elt); /* write preseved date */
+ }
+ else internal_date (tmp); /* get current date in IMAP format */
+ /* write header */
+ if (fprintf (df,"%s,%lu;%08lx%04lx-%08lx\015\012",tmp,i = SIZE (message),
+ uf,(unsigned long) f,au ? ++dstream->uid_last : 0) < 0)
+ ret = NIL;
+ else { /* write message */
+ size_t j;
+ if (!message->cursize) SETPOS (message,GETPOS (message));
+ while (i && (j = fwrite (message->curpos,1,message->cursize,df))) {
+ i -= j;
+ SETPOS (message,GETPOS (message) + j);
+ }
+ /* get next message */
+ if (i || !(*af) (dstream,data,&flags,&date,&message)) ret = NIL;
+ else if (au) mail_append_set (dst,dstream->uid_last);
+ }
+ }
+
+ /* if error... */
+ if (!ret || (fflush (df) == EOF)) {
+ /* revert file */
+ ftruncate (fd,sbuf.st_size);
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ if (errno) {
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+ ret = NIL;
+ }
+ if (au && ret) { /* return sets if doing APPENDUID */
+ (*au) (mailbox,dstream->uid_validity,dst);
+ fseek (df,15,SEEK_SET); /* update UIDLAST */
+ fprintf (df,"%08lx",dstream->uid_last);
+ }
+ else mail_free_searchset (&dst);
+ if (ret) times.actime = time (0) - 1;
+ /* else preserve \Marked status */
+ else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
+ sbuf.st_atime : time (0);
+ /* preserve mtime */
+ times.modtime = sbuf.st_mtime;
+ utime (file,&times); /* set the times */
+ fclose (df); /* close the file */
+ mm_nocritical (dstream); /* release critical */
+ }
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ if (dstream != stream) mail_close (dstream);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* MBX mail parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long mbx_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = NIL;
+ unsigned char c,*s,*t,*x;
+ char tmp[MAILTMPLEN];
+ unsigned long i,j,k,m;
+ off_t curpos = LOCAL->filesize;
+ unsigned long nmsgs = stream->nmsgs;
+ unsigned long recent = stream->recent;
+ unsigned long lastuid = 0;
+ short dirty = NIL;
+ short added = NIL;
+ short silent = stream->silent;
+ short uidwarn = T;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %lu to %lu!",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ lseek (LOCAL->fd,0,L_SET); /* rewind file */
+ /* read internal header */
+ read (LOCAL->fd,LOCAL->buf,HDRSIZE);
+ LOCAL->buf[HDRSIZE] = '\0'; /* tie off header */
+ c = LOCAL->buf[15]; /* save first character of last UID */
+ LOCAL->buf[15] = '\0';
+ /* parse UID validity */
+ stream->uid_validity = strtoul (LOCAL->buf + 7,NIL,16);
+ LOCAL->buf[15] = c; /* restore first character of last UID */
+ /* parse last UID */
+ i = strtoul (LOCAL->buf + 15,NIL,16);
+ stream->uid_last = stream->rdonly ? max (i,stream->uid_last) : i;
+ /* parse user flags */
+ for (i = 0, s = LOCAL->buf + 25;
+ (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s);
+ i++, s = t + 2) {
+ *t = '\0'; /* tie off flag */
+ if (!stream->user_flags[i] && (strlen (s) <= MAXUSERFLAG))
+ stream->user_flags[i] = cpystr (s);
+ }
+ LOCAL->ffuserflag = (int) i; /* first free user flag */
+
+ stream->silent = T; /* don't pass up mm_exists() events yet */
+ while (sbuf.st_size - curpos){/* while there is stuff to parse */
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,L_SET);
+ if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
+ sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size,
+ i ? strerror (errno) : "no data read");
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
+ if (!((s = strchr (LOCAL->buf,'\015')) && (s[1] == '\012'))) {
+ sprintf (tmp,"Unable to find CRLF at %lu in %lu bytes, text: %.80s",
+ (unsigned long) curpos,i,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ *s = '\0'; /* tie off header line */
+ i = (s + 2) - LOCAL->buf; /* note start of text offset */
+ if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
+ sprintf (tmp,"Unable to parse internal header at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ if (!(isxdigit (t[1]) && isxdigit (t[2]) && isxdigit (t[3]) &&
+ isxdigit (t[4]) && isxdigit (t[5]) && isxdigit (t[6]) &&
+ isxdigit (t[7]) && isxdigit (t[8]) && isxdigit (t[9]) &&
+ isxdigit (t[10]) && isxdigit (t[11]) && isxdigit (t[12]))) {
+ sprintf (tmp,"Unable to parse message flags at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ if ((t[13] != '-') || t[22] ||
+ !(isxdigit (t[14]) && isxdigit (t[15]) && isxdigit (t[16]) &&
+ isxdigit (t[17]) && isxdigit (t[18]) && isxdigit (t[19]) &&
+ isxdigit (t[20]) && isxdigit (t[21]))) {
+ sprintf (tmp,"Unable to parse message UID at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+
+ *s++ = '\0'; *t++ = '\0'; /* break up fields */
+ /* get message size */
+ if (!(j = strtoul (s,(char **) &x,10)) && (!(x && *x))) {
+ sprintf (tmp,"Unable to parse message size at %lu: %.80s,%.80s;%.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf,(char *) s,
+ (char *) t);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ /* make sure didn't run off end of file */
+ if (((off_t) (curpos + i + j)) > sbuf.st_size) {
+ sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
+ (unsigned long) curpos,(unsigned long) (curpos + i + j),
+ (unsigned long) sbuf.st_size);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ /* parse UID */
+ if ((m = strtoul (t+13,NIL,16)) &&
+ ((m <= lastuid) || (m > stream->uid_last))) {
+ if (uidwarn) {
+ sprintf (tmp,"Invalid UID %08lx in message %lu, rebuilding UIDs",
+ m,nmsgs+1);
+ mm_log (tmp,WARN);
+ uidwarn = NIL;
+ /* restart UID validity */
+ stream->uid_validity = (unsigned long) time (0);
+ }
+ m = 0; /* lose this UID */
+ dirty = T; /* mark dirty, set new lastuid */
+ stream->uid_last = lastuid;
+ }
+
+ t[12] = '\0'; /* parse system flags */
+ if ((k = strtoul (t+8,NIL,16)) & fEXPUNGED) {
+ if (m) lastuid = m; /* expunge message, update last UID seen */
+ else { /* no UID assigned? */
+ lastuid = ++stream->uid_last;
+ dirty = T;
+ }
+ }
+ else { /* not expunged, swell the cache */
+ added = T; /* note that a new message was added */
+ mail_exists (stream,++nmsgs);
+ /* instantiate an elt for this message */
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ /* parse the date */
+ if (!mail_parse_date (elt,LOCAL->buf)) {
+ sprintf (tmp,"Unable to parse message date at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* and internal header size */
+ elt->private.special.text.size = i;
+ /* header size not known yet */
+ elt->private.msg.header.text.size = 0;
+ elt->rfc822_size = j; /* note message size */
+ /* calculate system flags */
+ if (k & fSEEN) elt->seen = T;
+ if (k & fDELETED) elt->deleted = T;
+ if (k & fFLAGGED) elt->flagged = T;
+ if (k & fANSWERED) elt->answered = T;
+ if (k & fDRAFT) elt->draft = T;
+ t[8] = '\0'; /* get user flags value */
+ elt->user_flags = strtoul (t,NIL,16);
+ /* UID already assigned? */
+ if (!(elt->private.uid = m) || !(k & fOLD)) {
+ elt->recent = T; /* no, mark as recent */
+ ++recent; /* count up a new recent message */
+ dirty = T; /* and must rewrite header */
+ /* assign new UID */
+ if (!elt->private.uid) elt->private.uid = ++stream->uid_last;
+ mbx_update_status (stream,elt->msgno,NIL);
+ }
+ /* update last parsed UID */
+ lastuid = elt->private.uid;
+ }
+ curpos += i + j; /* update position */
+ }
+
+ if (dirty && !stream->rdonly){/* update header */
+ mbx_update_header (stream);
+ fsync (LOCAL->fd); /* make sure all the UID updates take */
+ }
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
+ LOCAL->filetime = sbuf.st_mtime;
+ if (added && !stream->rdonly){/* make sure atime updated */
+ struct utimbuf times;
+ times.actime = time (0);
+ times.modtime = LOCAL->filetime;
+ utime (stream->mailbox,&times);
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return LONGT; /* return the winnage */
+}
+
+/* MBX get cache element with status updating from file
+ * Accepts: MAIL stream
+ * message number
+ * expunge OK flag
+ * Returns: cache element
+ */
+
+MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok)
+{
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
+ old.answered = elt->answered; old.draft = elt->draft;
+ old.user_flags = elt->user_flags;
+ /* get new flags */
+ if (mbx_read_flags (stream,elt) && expok) {
+ mail_expunged (stream,elt->msgno);
+ return NIL; /* return this message was expunged */
+ }
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
+ (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
+ mm_flags (stream,msgno); /* let top level know */
+ return elt;
+}
+
+/* MBX read flags from file
+ * Accepts: MAIL stream
+ * cache element
+ * Returns: non-NIL if message expunged
+ */
+
+unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ unsigned long i;
+ struct stat sbuf;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* make sure file size is good */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag read!",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ fatal (LOCAL->buf);
+ }
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 24,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,14) < 0) {
+ sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) {
+ LOCAL->buf[14] = '\0'; /* tie off buffer for error message */
+ sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s",
+ elt->msgno,elt->private.special.offset,
+ elt->private.special.text.size,(char *) LOCAL->buf);
+ fatal (LOCAL->buf+50);
+ }
+ LOCAL->buf[13] = '\0'; /* tie off buffer */
+ /* calculate system flags */
+ i = strtoul (LOCAL->buf+9,NIL,16);
+ elt->seen = i & fSEEN ? T : NIL;
+ elt->deleted = i & fDELETED ? T : NIL;
+ elt->flagged = i & fFLAGGED ? T : NIL;
+ elt->answered = i & fANSWERED ? T : NIL;
+ elt->draft = i & fDRAFT ? T : NIL;
+ LOCAL->expunged |= i & fEXPUNGED ? T : NIL;
+ LOCAL->buf[9] = '\0'; /* tie off flags */
+ /* get user flags value */
+ elt->user_flags = strtoul (LOCAL->buf+1,NIL,16);
+ elt->valid = T; /* have valid flags now */
+ return i & fEXPUNGED;
+}
+
+/* MBX update header
+ * Accepts: MAIL stream
+ */
+
+#define NTKLUDGEOFFSET 7
+
+void mbx_update_header (MAILSTREAM *stream)
+{
+ int i;
+ char *s = LOCAL->buf;
+ memset (s,'\0',HDRSIZE); /* initialize header */
+ sprintf (s,"*mbx*\015\012%08lx%08lx\015\012",
+ stream->uid_validity,stream->uid_last);
+ for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; ++i)
+ sprintf (s += strlen (s),"%s\015\012",stream->user_flags[i]);
+ LOCAL->ffuserflag = i; /* first free user flag */
+ /* can we create more user flags? */
+ stream->kwd_create = (i < NUSERFLAGS) ? T : NIL;
+ /* write reserved lines */
+ while (i++ < NUSERFLAGS) strcat (s,"\015\012");
+ while (T) { /* rewind file */
+ lseek (LOCAL->fd,NTKLUDGEOFFSET,L_SET);
+ /* write new header */
+ if (write (LOCAL->fd,LOCAL->buf + NTKLUDGEOFFSET,
+ HDRSIZE - NTKLUDGEOFFSET) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+}
+
+/* MBX update status string
+ * Accepts: MAIL stream
+ * message number
+ * flags
+ */
+
+void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ /* readonly */
+ if (stream->rdonly || !elt->valid) mbx_read_flags (stream,elt);
+ else { /* readwrite */
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* make sure file size is good */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag update!",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ fatal (LOCAL->buf);
+ }
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 24,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,14) < 0) {
+ sprintf (LOCAL->buf,"Unable to read old status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) {
+ LOCAL->buf[14] = '\0'; /* tie off buffer for error message */
+ sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s",
+ elt->msgno,elt->private.special.offset,
+ elt->private.special.text.size,(char *) LOCAL->buf);
+ fatal (LOCAL->buf+50);
+ }
+ /* print new flag string */
+ sprintf (LOCAL->buf,"%08lx%04x-%08lx",elt->user_flags,(unsigned)
+ (((elt->deleted && flags) ?
+ fEXPUNGED : (strtoul (LOCAL->buf+9,NIL,16)) & fEXPUNGED) +
+ (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft) + fOLD),elt->private.uid);
+ while (T) { /* get to that place in the file */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 23,L_SET);
+ /* write new flags and UID */
+ if (write (LOCAL->fd,LOCAL->buf,21) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+ }
+}
+
+/* MBX locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * pointer to possible returned header
+ * Returns: position of header in file
+ */
+
+#define HDRBUFLEN 16384 /* good enough for most headers */
+#define SLOP 4 /* CR LF CR LF */
+
+unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size,char **hdr)
+{
+ unsigned long siz,done;
+ long i;
+ unsigned char *s,*t,*te;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ unsigned long ret = elt->private.special.offset +
+ elt->private.special.text.size;
+ if (hdr) *hdr = NIL; /* assume no header returned */
+ /* is header size known? */
+ if (*size = elt->private.msg.header.text.size) return ret;
+ /* paranoia check */
+ if (LOCAL->buflen < (HDRBUFLEN + SLOP))
+ fatal ("LOCAL->buf smaller than HDRBUFLEN");
+ lseek (LOCAL->fd,ret,L_SET); /* get to header position */
+ /* read HDRBUFLEN chunks with 4 byte slop */
+ for (done = siz = 0, s = LOCAL->buf;
+ (i = min ((long) (elt->rfc822_size - done),(long) HDRBUFLEN)) &&
+ (read (LOCAL->fd,s,i) == i);
+ done += i, siz += (t - LOCAL->buf) - SLOP, s = LOCAL->buf + SLOP) {
+ te = (t = s + i) - 12; /* calculate end of fast scan */
+ /* fast scan for CR */
+ for (s = LOCAL->buf; s < te;)
+ if (((*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
+ (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
+ (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
+ (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015')) &&
+ (*s == '\012') && (*++s == '\015') && (*++s == '\012')) {
+ *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf);
+ if (hdr) *hdr = LOCAL->buf;
+ return ret;
+ }
+ for (te = t - 3; (s < te);) /* final character-at-a-time scan */
+ if ((*s++ == '\015') && (*s == '\012') && (*++s == '\015') &&
+ (*++s == '\012')) {
+ *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf);
+ if (hdr) *hdr = LOCAL->buf;
+ return ret;
+ }
+ if (i <= SLOP) break; /* end of data */
+ /* slide over last 4 bytes */
+ memmove (LOCAL->buf,t - SLOP,SLOP);
+ hdr = NIL; /* can't return header this way */
+ }
+ /* not found: header consumes entire message */
+ elt->private.msg.header.text.size = *size = elt->rfc822_size;
+ if (hdr) *hdr = LOCAL->buf; /* possibly return header too */
+ return ret;
+}
+
+/* MBX mail rewrite mailbox
+ * Accepts: MAIL stream
+ * pointer to return reclaimed size
+ * flags (0 = no expunge, 1 = expunge deleted, -1 = expunge sequence)
+ * Returns: number of expunged messages
+ */
+
+unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed,
+ long flags)
+{
+ struct utimbuf times;
+ struct stat sbuf;
+ off_t pos,ppos;
+ int ld;
+ unsigned long i,j,k,m,delta;
+ unsigned long n = *reclaimed = 0;
+ unsigned long recent = 0;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ /* get parse/append permission */
+ if ((ld = lockname (lock,stream->mailbox,LOCK_EX)) < 0) {
+ mm_log ("Unable to lock expunge mailbox",ERROR);
+ return 0;
+ }
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime && !LOCAL->flagcheck &&
+ (LOCAL->filetime < sbuf.st_mtime)) LOCAL->flagcheck = T;
+ if (!mbx_parse (stream)) { /* make sure see any newly-arrived messages */
+ unlockfd (ld,lock); /* failed?? */
+ return 0;
+ }
+ if (LOCAL->flagcheck) { /* sweep flags if need flagcheck */
+ LOCAL->filetime = sbuf.st_mtime;
+ for (i = 1; i <= stream->nmsgs; ++i) mbx_elt (stream,i,NIL);
+ LOCAL->flagcheck = NIL;
+ }
+
+ /* get exclusive access */
+ if (!flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
+ mm_critical (stream); /* go critical */
+ for (i = 1,delta = 0,pos = ppos = HDRSIZE; i <= stream->nmsgs; ) {
+ /* note if message not at predicted location */
+ if (m = (elt = mbx_elt (stream,i,NIL))->private.special.offset - ppos) {
+ ppos = elt->private.special.offset;
+ *reclaimed += m; /* note reclaimed message space */
+ delta += m; /* and as expunge delta */
+ }
+ /* number of bytes to smash or preserve */
+ ppos += (k = elt->private.special.text.size + elt->rfc822_size);
+ /* if need to expunge this message*/
+ if (flags && elt->deleted && ((flags > 0) || elt->sequence)) {
+ delta += k; /* number of bytes to delete */
+ mail_expunged(stream,i);/* notify upper levels */
+ n++; /* count up one more expunged message */
+ }
+ else { /* preserved message */
+ i++; /* count this message */
+ if (elt->recent) ++recent;
+ if (delta) { /* moved, note first byte to preserve */
+ j = elt->private.special.offset;
+ do { /* read from source position */
+ m = min (k,LOCAL->buflen);
+ lseek (LOCAL->fd,j,L_SET);
+ read (LOCAL->fd,LOCAL->buf,m);
+ pos = j - delta; /* write to destination position */
+ while (T) {
+ lseek (LOCAL->fd,pos,L_SET);
+ if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+ pos += m; /* new position */
+ j += m; /* next chunk, perhaps */
+ } while (k -= m); /* until done */
+ /* note the new address of this text */
+ elt->private.special.offset -= delta;
+ }
+ /* preserved but no deleted messages yet */
+ else pos = elt->private.special.offset + k;
+ }
+ }
+ /* deltaed file size match position? */
+ if (m = (LOCAL->filesize -= delta) - pos) {
+ *reclaimed += m; /* probably an fEXPUNGED msg */
+ LOCAL->filesize = pos; /* set correct size */
+ }
+ /* truncate file after last message */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ fsync (LOCAL->fd); /* force disk update */
+ mm_nocritical (stream); /* release critical */
+ flock (LOCAL->fd,LOCK_SH); /* allow sharers again */
+ }
+
+ else { /* can't get exclusive */
+ flock (LOCAL->fd,LOCK_SH); /* recover previous shared mailbox lock */
+ /* do hide-expunge when shared */
+ if (flags) for (i = 1; i <= stream->nmsgs; ) {
+ if (elt = mbx_elt (stream,i,T)) {
+ /* make the message invisible */
+ if (elt->deleted && ((flags > 0) || elt->sequence)) {
+ mbx_update_status (stream,elt->msgno,LONGT);
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else {
+ i++; /* preserved message */
+ if (elt->recent) ++recent;
+ }
+ }
+ else n++; /* count up one more expunged message */
+ }
+ fsync (LOCAL->fd); /* force disk update */
+ }
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* reset atime to now */
+ utime (stream->mailbox,&times);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ return n; /* return number of expunged messages */
+}
+
+/* MBX mail lock for flag updating
+ * Accepts: stream
+ * Returns: T if successful, NIL if failure
+ */
+
+long mbx_flaglock (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ unsigned long i;
+ int ld;
+ char lock[MAILTMPLEN];
+ /* no-op if readonly or already locked */
+ if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld < 0)) {
+ /* lock now */
+ if ((ld = lockname (lock,stream->mailbox,LOCK_EX)) < 0) return NIL;
+ if (!LOCAL->flagcheck) { /* don't do this if flagcheck already needed */
+ if (LOCAL->filetime) { /* know previous time? */
+ fstat (LOCAL->fd,&sbuf);/* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->flagcheck = T;
+ LOCAL->filetime = 0; /* don't do this test for any other messages */
+ }
+ if (!mbx_parse (stream)) {/* parse mailbox */
+ unlockfd (ld,lock); /* shouldn't happen */
+ return NIL;
+ }
+ if (LOCAL->flagcheck) /* invalidate cache if flagcheck */
+ for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->valid = NIL;
+ }
+ LOCAL->ld = ld; /* copy to stream for subsequent calls */
+ memcpy (LOCAL->lock,lock,MAILTMPLEN);
+ }
+ return LONGT;
+}
diff --git a/imap/src/osdep/nt/mkautaux.bat b/imap/src/osdep/nt/mkautaux.bat
new file mode 100755
index 00000000..c65022d2
--- /dev/null
+++ b/imap/src/osdep/nt/mkautaux.bat
@@ -0,0 +1,31 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Authenticator Linkage Generator auxillary for NT/Win9x
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 6 December 1995
+REM Last Edited:30 August 2006
+
+ECHO extern AUTHENTICATOR auth_%1; >> LINKAGE.H
+REM Note the introduction of the caret to quote the ampersand in NT
+if "%OS%" == "Windows_NT" ECHO auth_link (^&auth_%1); /* link in the %1 authenticator */ >> LINKAGE.C
+if "%OS%" == "" ECHO auth_link (&auth_%1); /* link in the %1 authenticator */ >> LINKAGE.C
+ECHO #include "auth_%1.c" >> AUTHS.C
diff --git a/imap/src/osdep/nt/mkauths.bat b/imap/src/osdep/nt/mkauths.bat
new file mode 100755
index 00000000..d8c5e360
--- /dev/null
+++ b/imap/src/osdep/nt/mkauths.bat
@@ -0,0 +1,33 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Authenticator Linkage Generator for DOS and Windows
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 6 December 1995
+REM Last Edited:30 August 2006
+
+REM Erase old authenticators list
+IF EXIST AUTHS.C DEL AUTHS.C
+
+REM Now define the new list
+FOR %%D IN (%1 %2 %3 %4 %5 %6 %7 %8 %9) DO CALL MKAUTAUX %%D
+
+EXIT 0
diff --git a/imap/src/osdep/nt/mtxnt.c b/imap/src/osdep/nt/mtxnt.c
new file mode 100644
index 00000000..13f61586
--- /dev/null
+++ b/imap/src/osdep/nt/mtxnt.c
@@ -0,0 +1,1232 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MTX mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 22 May 1990
+ * Last Edited: 15 June 2007
+ */
+
+
+/* FILE TIME SEMANTICS
+ *
+ * The atime is the last read time of the file.
+ * The mtime is the last flags update time of the file.
+ * The ctime is the last write time of the file.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/utime.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+/* MTX I/O stream local data */
+
+typedef struct mtx_local {
+ unsigned int shouldcheck: 1; /* if ping should do a check instead */
+ unsigned int mustcheck: 1; /* if ping must do a check instead */
+ int fd; /* file descriptor for I/O */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* last snarf time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+} MTXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MTXLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *mtx_valid (char *name);
+int mtx_isvalid (char *name,char *file);
+void *mtx_parameters (long function,void *value);
+void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void mtx_list (MAILSTREAM *stream,char *ref,char *pat);
+void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mtx_create (MAILSTREAM *stream,char *mailbox);
+long mtx_delete (MAILSTREAM *stream,char *mailbox);
+long mtx_rename (MAILSTREAM *stream,char *old,char *newname);
+long mtx_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *mtx_open (MAILSTREAM *stream);
+void mtx_close (MAILSTREAM *stream,long options);
+void mtx_flags (MAILSTREAM *stream,char *sequence,long flags);
+char *mtx_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void mtx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long mtx_ping (MAILSTREAM *stream);
+void mtx_check (MAILSTREAM *stream);
+void mtx_snarf (MAILSTREAM *stream);
+long mtx_expunge (MAILSTREAM *stream,char *sequence,long options);
+long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+long mtx_parse (MAILSTREAM *stream);
+MESSAGECACHE *mtx_elt (MAILSTREAM *stream,unsigned long msgno);
+void mtx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
+void mtx_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag);
+unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size);
+
+
+/* MTX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mtxdriver = {
+ "mtx", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_CRLF|DR_NOSTICKY,
+ (DRIVER *) NIL, /* next driver */
+ mtx_valid, /* mailbox is valid for us */
+ mtx_parameters, /* manipulate parameters */
+ mtx_scan, /* scan mailboxes */
+ mtx_list, /* list mailboxes */
+ mtx_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ mtx_create, /* create mailbox */
+ mtx_delete, /* delete mailbox */
+ mtx_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ mtx_open, /* open mailbox */
+ mtx_close, /* close mailbox */
+ mtx_flags, /* fetch message "fast" attributes */
+ mtx_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mtx_header, /* fetch message header */
+ mtx_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ mtx_flag, /* modify flags */
+ mtx_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mtx_ping, /* ping mailbox to see if still alive */
+ mtx_check, /* check for new messages */
+ mtx_expunge, /* expunge deleted messages */
+ mtx_copy, /* copy messages to another mailbox */
+ mtx_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mtxproto = {&mtxdriver};
+
+/* MTX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mtx_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return mtx_isvalid (name,tmp) ? &mtxdriver : NIL;
+}
+
+
+/* MTX mail test for valid mailbox
+ * Accepts: mailbox name
+ * buffer to return file name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int mtx_isvalid (char *name,char *file)
+{
+ int fd;
+ int ret = NIL;
+ char *s,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ struct utimbuf times;
+ errno = EINVAL; /* assume invalid argument */
+ /* if file, get its status */
+ if ((s = dummy_file (file,name)) && !stat (s,&sbuf) &&
+ ((sbuf.st_mode & S_IFMT) == S_IFREG)) {
+ if (!sbuf.st_size)errno = 0;/* empty file */
+ else if ((fd = open (file,O_BINARY|O_RDONLY,NIL)) >= 0) {
+ memset (tmp,'\0',MAILTMPLEN);
+ if ((read (fd,tmp,64) >= 0) && (s = strchr (tmp,'\015')) &&
+ (s[1] == '\012')) { /* valid format? */
+ *s = '\0'; /* tie off header */
+ /* must begin with dd-mmm-yy" */
+ ret = (((tmp[2] == '-' && tmp[6] == '-') ||
+ (tmp[1] == '-' && tmp[5] == '-')) &&
+ (s = strchr (tmp+18,',')) && strchr (s+2,';')) ? T : NIL;
+ }
+ else errno = -1; /* bogus format */
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if (sbuf.st_ctime > sbuf.st_atime) {
+ /* preserve atime and mtime */
+ times.actime = sbuf.st_atime;
+ times.modtime = sbuf.st_mtime;
+ utime (file,&times); /* set the times */
+ }
+ }
+ }
+ /* in case INBOX but not mtx format */
+ else if ((errno == ENOENT) && !compare_cstring (name,"INBOX")) errno = -1;
+ return ret; /* return what we should */
+}
+
+
+/* MTX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mtx_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* MTX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MTX mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mtx_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MTX mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MTX mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,mbx[MAILTMPLEN];
+ if (s = dummy_file (mbx,mailbox)) return dummy_create (stream,s);
+ sprintf (mbx,"Can't create %.80s: invalid name",mailbox);
+ mm_log (mbx,ERROR);
+ return NIL;
+}
+
+
+/* MTX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return mtx_rename (stream,mailbox,NIL);
+}
+
+/* MTX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = LONGT;
+ char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ if (!dummy_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'\\')) && !s[1])))) {
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ if ((fd = open (file,O_BINARY|O_RDWR,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get exclusive parse/append permission */
+ if ((ld = lockname (lock,file,LOCK_EX)) < 0) {
+ mm_log ("Unable to lock rename mailbox",ERROR);
+ return NIL;
+ }
+ /* lock out other users */
+ if (flock (fd,LOCK_EX|LOCK_NB)) {
+ close (fd); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ mm_log (tmp,ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return NIL;
+ }
+
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if ((s = strrchr (tmp,'\\')) && (s != tmp) &&
+ ((tmp[1] != ':') || (s != tmp + 2))) {
+ c = s[1]; /* remember character after delimiter */
+ *s = s[1] = '\0'; /* tie off name at delimiter */
+ /* name doesn't exist, create it */
+ if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
+ *s = '\\'; /* restore delimiter */
+ if (!dummy_create (stream,tmp)) ret = NIL;
+ }
+ else *s = '\\'; /* restore delimiter */
+ s[1] = c; /* restore character after delimiter */
+ }
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* pacify NTFS */
+ /* rename the file */
+ if (ret && rename (file,tmp)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ else {
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* pacify NTFS */
+ if (unlink (file)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %.80s",old,strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return ret; /* return success */
+}
+
+/* MTX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mtx_open (MAILSTREAM *stream)
+{
+ int fd,ld;
+ char tmp[MAILTMPLEN];
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &mtxproto;
+ if (stream->local) fatal ("mtx recycle stream");
+ /* canonicalize the mailbox name */
+ if (!dummy_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ }
+ if (stream->rdonly ||
+ (fd = open (tmp,O_BINARY|O_RDWR,NIL)) < 0) {
+ if ((fd = open (tmp,O_BINARY|O_RDONLY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox: %.80s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ else if (!stream->rdonly) { /* got it, but readonly */
+ mm_log ("Can't get write access to mailbox, access is readonly",WARN);
+ stream->rdonly = T;
+ }
+ }
+ stream->local = fs_get (sizeof (MTXLOCAL));
+ LOCAL->fd = fd; /* bind the file */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ /* get shared parse permission */
+ if ((ld = lockname (tmp,stream->mailbox,LOCK_SH)) < 0) {
+ mm_log ("Unable to lock open mailbox",ERROR);
+ return NIL;
+ }
+ flock (LOCAL->fd,LOCK_SH); /* lock the file */
+ unlockfd (ld,tmp); /* release shared parse permission */
+ LOCAL->filesize = 0; /* initialize parsed file size */
+ LOCAL->filetime = 0; /* time not set up yet */
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ stream->sequence++; /* bump sequence number */
+ stream->uid_validity = (unsigned long) time (0);
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (mtx_ping (stream) && !stream->nmsgs)
+ mm_log ("Mailbox is empty",(long) NIL);
+ if (!LOCAL) return NIL; /* failure if stream died */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft =
+ stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ return stream; /* return stream to caller */
+}
+
+/* MTX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mtx_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ if (options & CL_EXPUNGE) mtx_expunge (stream,NIL,NIL);
+ stream->silent = silent; /* restore previous status */
+ flock (LOCAL->fd,LOCK_UN); /* unlock local file */
+ close (LOCAL->fd); /* close the local file */
+ /* free local text buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+
+/* MTX mail fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ * Sniffs at file to see if some other process changed the flags
+ */
+
+void mtx_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ unsigned long i;
+ if (mtx_ping (stream) && /* ping mailbox, get new status for messages */
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if (mail_elt (stream,i)->sequence) mtx_elt (stream,i);
+}
+
+/* MTX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mtx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get to header position */
+ lseek (LOCAL->fd,mtx_hdrpos (stream,msgno,length),L_SET);
+ /* is buffer big enough? */
+ if (*length > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = *length) + 1);
+ }
+ LOCAL->buf[*length] = '\0'; /* tie off string */
+ /* slurp the data */
+ read (LOCAL->fd,LOCAL->buf,*length);
+ return LOCAL->buf;
+}
+
+/* MTX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: T, always
+ */
+
+long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ FDDATA d;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mtx_elt (stream,msgno); /* get message status */
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ elt->seen = T; /* mark message as seen */
+ /* recalculate status */
+ mtx_update_status (stream,msgno,NIL);
+ mm_flags (stream,msgno);
+ }
+ /* find header position */
+ i = mtx_hdrpos (stream,msgno,&j);
+ d.fd = LOCAL->fd; /* set up file descriptor */
+ d.pos = i + j;
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE;
+ INIT (bs,fd_string,&d,elt->rfc822_size - j);
+ return T; /* success */
+}
+
+/* MTX mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ */
+
+void mtx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ struct utimbuf times;
+ struct stat sbuf;
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* make sure read comes after all that */
+ utime (stream->mailbox,&times);
+ }
+}
+
+
+/* MTX mail per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ struct stat sbuf;
+ /* maybe need to do a checkpoint? */
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ LOCAL->filetime = 0; /* don't do this test for any other messages */
+ }
+ /* recalculate status */
+ mtx_update_status (stream,elt->msgno,NIL);
+}
+
+/* MTX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long mtx_ping (MAILSTREAM *stream)
+{
+ unsigned long i = 1;
+ long r = T;
+ int ld;
+ char lock[MAILTMPLEN];
+ struct stat sbuf;
+ if (stream && LOCAL) { /* only if stream already open */
+ fstat (LOCAL->fd,&sbuf); /* get current file poop */
+ if (LOCAL->filetime && !(LOCAL->mustcheck || LOCAL->shouldcheck) &&
+ (LOCAL->filetime < sbuf.st_mtime)) LOCAL->shouldcheck = T;
+ /* check for changed message status */
+ if (LOCAL->mustcheck || LOCAL->shouldcheck) {
+ LOCAL->filetime = sbuf.st_mtime;
+ if (LOCAL->shouldcheck) /* babble when we do this unilaterally */
+ mm_notify (stream,"[CHECK] Checking for flag updates",NIL);
+ while (i <= stream->nmsgs) mtx_elt (stream,i++);
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ }
+ /* get shared parse/append permission */
+ if ((sbuf.st_size != LOCAL->filesize) &&
+ ((ld = lockname (lock,stream->mailbox,LOCK_SH)) >= 0)) {
+ /* parse resulting mailbox */
+ r = (mtx_parse (stream)) ? T : NIL;
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ }
+ return r; /* return result of the parse */
+}
+
+
+/* MTX mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void mtx_check (MAILSTREAM *stream)
+{
+ /* mark that a check is desired */
+ if (LOCAL) LOCAL->mustcheck = T;
+ if (mtx_ping (stream)) mm_log ("Check completed",(long) NIL);
+}
+
+/* MTX mail expunge mailbox
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long mtx_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ struct utimbuf times;
+ struct stat sbuf;
+ off_t pos = 0;
+ int ld;
+ unsigned long i = 1;
+ unsigned long j,k,m,recent;
+ unsigned long n = 0;
+ unsigned long delta = 0;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ if (!(ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ mtx_ping (stream))); /* parse sequence if given, ping stream */
+ else if (stream->rdonly) mm_log ("Expunge ignored on readonly mailbox",WARN);
+ else {
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ }
+ /* get exclusive parse/append permission */
+ if ((ld = lockname (lock,stream->mailbox,LOCK_EX)) < 0)
+ mm_log ("Unable to lock expunge mailbox",ERROR);
+ /* make sure see any newly-arrived messages */
+ else if (!mtx_parse (stream));
+ /* get exclusive access */
+ else if (flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
+ flock (LOCAL->fd,LOCK_SH);/* recover previous lock */
+ mm_log ("Can't expunge because mailbox is in use by another process",
+ ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+
+ else {
+ mm_critical (stream); /* go critical */
+ recent = stream->recent; /* get recent now that pinged and locked */
+ /* for each message */
+ while (i <= stream->nmsgs) {
+ /* get cache element */
+ elt = mtx_elt (stream,i);
+ /* number of bytes to smash or preserve */
+ k = elt->private.special.text.size + elt->rfc822_size;
+ /* if need to expunge this message */
+ if (elt->deleted && (sequence ? elt->sequence : T)) {
+ /* if recent, note one less recent message */
+ if (elt->recent) --recent;
+ delta += k; /* number of bytes to delete */
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else if (i++ && delta) {/* preserved message */
+ /* first byte to preserve */
+ j = elt->private.special.offset;
+ do { /* read from source position */
+ m = min (k,LOCAL->buflen);
+ lseek (LOCAL->fd,j,L_SET);
+ read (LOCAL->fd,LOCAL->buf,m);
+ pos = j - delta; /* write to destination position */
+ while (T) {
+ lseek (LOCAL->fd,pos,L_SET);
+ if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+ pos += m; /* new position */
+ j += m; /* next chunk, perhaps */
+ } while (k -= m); /* until done */
+ /* note the new address of this text */
+ elt->private.special.offset -= delta;
+ }
+ /* preserved but no deleted messages */
+ else pos = elt->private.special.offset + k;
+ }
+ if (n) { /* truncate file after last message */
+ if (pos != (LOCAL->filesize -= delta)) {
+ sprintf (LOCAL->buf,
+ "Calculated size mismatch %lu != %lu, delta = %lu",
+ (unsigned long) pos,(unsigned long) LOCAL->filesize,delta);
+ mm_log (LOCAL->buf,WARN);
+ LOCAL->filesize = pos;/* fix it then */
+ }
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ sprintf (LOCAL->buf,"Expunged %lu messages",n);
+ /* output the news */
+ mm_log (LOCAL->buf,(long) NIL);
+ }
+ else mm_log ("No messages deleted, so no update needed",(long) NIL);
+ fsync (LOCAL->fd); /* force disk update */
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* reset atime to now */
+ utime (stream->mailbox,&times);
+ mm_nocritical (stream); /* release critical */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ flock (LOCAL->fd,LOCK_SH);/* allow sharers again */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+ }
+ return ret;
+}
+
+/* MTX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ struct utimbuf times;
+ MESSAGECACHE *elt;
+ unsigned long i,j,k;
+ long ret = LONGT;
+ int fd,ld;
+ char file[MAILTMPLEN],lock[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ /* make sure valid mailbox */
+ if (!mtx_isvalid (mailbox,file)) switch (errno) {
+ case ENOENT: /* no such file? */
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid MTX-format mailbox name: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a MTX-format mailbox: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* got file? */
+ if ((fd = open (file,O_BINARY|O_RDWR|O_CREAT,S_IREAD|S_IWRITE)) < 0) {
+ sprintf (LOCAL->buf,"Unable to open copy mailbox: %.80s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ mm_critical (stream); /* go critical */
+ /* get exclusive parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockname (lock,file,LOCK_EX)) < 0)) {
+ mm_log ("Unable to lock copy mailbox",ERROR);
+ mm_nocritical (stream);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
+
+ /* for each requested message */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ /* number of bytes to copy */
+ k = elt->private.special.text.size + elt->rfc822_size;
+ do { /* read from source position */
+ j = min (k,LOCAL->buflen);
+ read (LOCAL->fd,LOCAL->buf,j);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ } while (ret && (k -= j));/* until done */
+ }
+ /* make sure all the updates take */
+ if (!(ret && (ret = !fsync (fd)))) {
+ sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ }
+ /* set atime to now-1 if successful copy */
+ if (ret) times.actime = time (0) - 1;
+ /* else preserved \Marked status */
+ else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
+ sbuf.st_atime : time (0);
+ times.modtime = sbuf.st_mtime;/* preserve mtime */
+ utime (file,&times); /* set the times */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ close (fd); /* close the file */
+ mm_nocritical (stream); /* release critical */
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE)) {
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mtx_elt (stream,i))->sequence) {
+ elt->deleted = T; /* mark message deleted */
+ /* recalculate status */
+ mtx_update_status (stream,i,NIL);
+ }
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* make sure atime remains greater */
+ utime (stream->mailbox,&times);
+ }
+ }
+ if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
+ mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN);
+ return ret;
+}
+
+/* MTX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd,ld,c;
+ char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ struct utimbuf times;
+ FILE *df;
+ MESSAGECACHE elt;
+ long f;
+ unsigned long i,uf;
+ STRING *message;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = &mtxproto;
+ /* make sure valid mailbox */
+ if (!mtx_isvalid (mailbox,file)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!compare_cstring (mailbox,"INBOX")) mtx_create (NIL,"INBOX");
+ else {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* falls through */
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid MTX-format mailbox name: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MTX-format mailbox: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get first message */
+ if (!(*af) (stream,data,&flags,&date,&message)) return NIL;
+
+ /* open destination mailbox */
+ if (((fd = open (file,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IREAD|S_IWRITE))
+ < 0) || !(df = fdopen (fd,"ab"))) {
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockname (lock,file,LOCK_EX)) < 0)) {
+ mm_log ("Unable to lock append mailbox",ERROR);
+ close (fd);
+ return NIL;
+ }
+ mm_critical (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ errno = 0;
+ do { /* parse flags */
+ if (!SIZE (message)) { /* guard against zero-length */
+ mm_log ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ f = mail_parse_flags (stream,flags,&i);
+ /* reverse bits (dontcha wish we had CIRC?) */
+ for (uf = 0; i; uf |= 1 << (29 - find_rightmost_bit (&i)));
+ if (date) { /* parse date if given */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ mm_log (tmp,ERROR);
+ ret = NIL; /* mark failure */
+ break;
+ }
+ mail_date (tmp,&elt); /* write preseved date */
+ }
+ else internal_date (tmp); /* get current date in IMAP format */
+ /* write header */
+ if (fprintf (df,"%s,%lu;%010lo%02lo\015\012",tmp,i = SIZE (message),uf,
+ (unsigned long) f) < 0) ret = NIL;
+ else { /* write message */
+ if (i) do c = 0xff & SNX (message);
+ while ((putc (c,df) != EOF) && --i);
+ /* get next message */
+ if (i || !(*af) (stream,data,&flags,&date,&message)) ret = NIL;
+ }
+ } while (ret && message);
+ /* if error... */
+ if (!ret || (fflush (df) == EOF)) {
+ ftruncate (fd,sbuf.st_size);/* revert file */
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ if (errno) {
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+ ret = NIL;
+ }
+ if (ret) times.actime = time (0) - 1;
+ /* else preserved \Marked status */
+ else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
+ sbuf.st_atime : time (0);
+ times.modtime = sbuf.st_mtime;/* preserve mtime */
+ utime (file,&times); /* set the times */
+ fclose (df); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ mm_nocritical (stream); /* release critical */
+ if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
+ mm_log ("Can not return meaningful APPENDUID with this mailbox format",
+ WARN);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* MTX mail parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long mtx_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = NIL;
+ unsigned char c,*s,*t,*x;
+ char tmp[MAILTMPLEN];
+ unsigned long i,j;
+ long curpos = LOCAL->filesize;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ short added = NIL;
+ short silent = stream->silent;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %ld to %ld!",curpos,sbuf.st_size);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ stream->silent = T; /* don't pass up mm_exists() events yet */
+ while (sbuf.st_size - curpos){/* while there is stuff to parse */
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,L_SET);
+ if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
+ sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size,
+ i ? strerror (errno) : "no data read");
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
+ if (!((s = strchr (LOCAL->buf,'\015')) && (s[1] == '\012'))) {
+ sprintf (tmp,"Unable to find CRLF at %lu in %lu bytes, text: %s",
+ (unsigned long) curpos,i,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ *s = '\0'; /* tie off header line */
+ i = (s + 2) - LOCAL->buf; /* note start of text offset */
+ if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
+ sprintf (tmp,"Unable to parse internal header at %lu: %s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ *s++ = '\0'; *t++ = '\0'; /* tie off fields */
+
+ added = T; /* note that a new message was added */
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ /* instantiate an elt for this message */
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ elt->private.uid = ++stream->uid_last;
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* in case error */
+ elt->private.special.text.size = 0;
+ /* header size not known yet */
+ elt->private.msg.header.text.size = 0;
+ x = s; /* parse the header components */
+ if (mail_parse_date (elt,LOCAL->buf) &&
+ (elt->rfc822_size = strtoul (s,(char **) &s,10)) && (!(s && *s)) &&
+ isdigit (t[0]) && isdigit (t[1]) && isdigit (t[2]) &&
+ isdigit (t[3]) && isdigit (t[4]) && isdigit (t[5]) &&
+ isdigit (t[6]) && isdigit (t[7]) && isdigit (t[8]) &&
+ isdigit (t[9]) && isdigit (t[10]) && isdigit (t[11]) && !t[12])
+ elt->private.special.text.size = i;
+ else { /* oops */
+ sprintf (tmp,"Unable to parse internal header elements at %ld: %s,%s;%s",
+ curpos,(char *) LOCAL->buf,(char *) x,(char *) t);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ /* make sure didn't run off end of file */
+ if ((curpos += (elt->rfc822_size + i)) > sbuf.st_size) {
+ sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
+ elt->private.special.offset,(unsigned long) curpos,
+ (unsigned long) sbuf.st_size);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ c = t[10]; /* remember first system flags byte */
+ t[10] = '\0'; /* tie off flags */
+ j = strtoul (t,NIL,8); /* get user flags value */
+ t[10] = c; /* restore first system flags byte */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ /* calculate system flags */
+ if ((j = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T;
+ if (j & fDELETED) elt->deleted = T;
+ if (j & fFLAGGED) elt->flagged = T;
+ if (j & fANSWERED) elt->answered = T;
+ if (j & fDRAFT) elt->draft = T;
+ if (!(j & fOLD)) { /* newly arrived message? */
+ elt->recent = T;
+ recent++; /* count up a new recent message */
+ /* mark it as old */
+ mtx_update_status (stream,nmsgs,NIL);
+ }
+ }
+ fsync (LOCAL->fd); /* make sure all the fOLD flags take */
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
+ LOCAL->filetime = sbuf.st_mtime;
+ if (added && !stream->rdonly){/* make sure atime updated */
+ struct utimbuf times;
+ times.actime = time (0);
+ times.modtime = LOCAL->filetime;
+ utime (stream->mailbox,&times);
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return LONGT; /* return the winnage */
+}
+
+/* MTX get cache element with status updating from file
+ * Accepts: MAIL stream
+ * message number
+ * Returns: cache element
+ */
+
+MESSAGECACHE *mtx_elt (MAILSTREAM *stream,unsigned long msgno)
+{
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
+ old.answered = elt->answered; old.draft = elt->draft;
+ old.user_flags = elt->user_flags;
+ mtx_read_flags (stream,elt);
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
+ (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
+ mm_flags (stream,msgno); /* let top level know */
+ return elt;
+}
+
+/* MTX read flags from file
+ * Accepts: MAIL stream
+ * Returns: cache element
+ */
+
+void mtx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ unsigned long i,j;
+ /* noop if readonly and have valid flags */
+ if (stream->rdonly && elt->valid) return;
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 14,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,12) < 0) {
+ sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ /* calculate system flags */
+ i = (((LOCAL->buf[10]-'0') * 8) + LOCAL->buf[11]-'0');
+ elt->seen = i & fSEEN ? T : NIL; elt->deleted = i & fDELETED ? T : NIL;
+ elt->flagged = i & fFLAGGED ? T : NIL;
+ elt->answered = i & fANSWERED ? T : NIL; elt->draft = i & fDRAFT ? T : NIL;
+ LOCAL->buf[10] = '\0'; /* tie off flags */
+ j = strtoul(LOCAL->buf,NIL,8);/* get user flags value */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ elt->valid = T; /* have valid flags now */
+}
+
+/* MTX update status string
+ * Accepts: MAIL stream
+ * message number
+ * flag saying whether or not to sync
+ */
+
+void mtx_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag)
+{
+ struct utimbuf times;
+ struct stat sbuf;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ unsigned long j,k = 0;
+ /* readonly */
+ if (stream->rdonly || !elt->valid) mtx_read_flags (stream,elt);
+ else { /* readwrite */
+ j = elt->user_flags; /* get user flags */
+ /* reverse bits (dontcha wish we had CIRC?) */
+ while (j) k |= 1 << (29 - find_rightmost_bit (&j));
+ /* print new flag string */
+ sprintf (LOCAL->buf,"%010lo%02o",k,(unsigned)
+ (fOLD + (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ while (T) { /* get to that place in the file */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 14,L_SET);
+ /* write new flags */
+ if (write (LOCAL->fd,LOCAL->buf,12) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+ if (syncflag) { /* sync if requested */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* make sure read is later */
+ utime (stream->mailbox,&times);
+ }
+ }
+}
+
+/* MTX locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * Returns: position of header in file
+ */
+
+unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size)
+{
+ unsigned long siz;
+ long i = 0;
+ int q = 0;
+ char *s,tmp[MAILTMPLEN];
+ MESSAGECACHE *elt = mtx_elt (stream,msgno);
+ unsigned long ret = elt->private.special.offset +
+ elt->private.special.text.size;
+ /* is header size known? */
+ if (!(*size = elt->private.msg.header.text.size)) {
+ lseek (LOCAL->fd,ret,L_SET);/* get to header position */
+ /* search message for CRLF CRLF */
+ for (siz = 1,s = tmp; siz <= elt->rfc822_size; siz++) {
+ /* read another buffer as necessary */
+ if ((--i <= 0) && /* buffer empty? */
+ (read (LOCAL->fd,s = tmp,
+ i = min (elt->rfc822_size - siz,(long) MAILTMPLEN)) < 0))
+ return ret; /* I/O error? */
+ switch (q) { /* sniff at buffer */
+ case 0: /* first character */
+ q = (*s++ == '\015') ? 1 : 0;
+ break;
+ case 1: /* second character */
+ q = (*s++ == '\012') ? 2 : 0;
+ break;
+ case 2: /* third character */
+ q = (*s++ == '\015') ? 3 : 0;
+ break;
+ case 3: /* fourth character */
+ if (*s++ == '\012') { /* have the sequence? */
+ /* yes, note for later */
+ elt->private.msg.header.text.size = *size = siz;
+ return ret;
+ }
+ q = 0; /* lost... */
+ break;
+ }
+ }
+ /* header consumes entire message */
+ elt->private.msg.header.text.size = *size = elt->rfc822_size;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/nt/nl_nt.c b/imap/src/osdep/nt/nl_nt.c
new file mode 100644
index 00000000..47cb7f0a
--- /dev/null
+++ b/imap/src/osdep/nt/nl_nt.c
@@ -0,0 +1,61 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Windows/TOPS-20 newline routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Copy string with CRLF newlines
+ * Accepts: destination string
+ * pointer to size of destination string buffer
+ * source string
+ * length of source string
+ * Returns: length of copied string
+ */
+
+unsigned long strcrlfcpy (unsigned char **dst,unsigned long *dstl,
+ unsigned char *src,unsigned long srcl)
+{
+ /* flush destination buffer if too small */
+ if (*dst && (srcl > *dstl)) fs_give ((void **) dst);
+ if (!*dst) { /* make a new buffer if needed */
+ *dst = (char *) fs_get ((size_t) (*dstl = srcl) + 1);
+ if (dstl) *dstl = srcl; /* return new buffer length to main program */
+ }
+ /* copy strings */
+ if (srcl) memcpy (*dst,src,(size_t) srcl);
+ *(*dst + srcl) = '\0'; /* tie off destination */
+ return srcl; /* return length */
+}
+
+
+/* Length of string after strcrlfcpy applied
+ * Accepts: source string
+ * Returns: length of string
+ */
+
+unsigned long strcrlflen (STRING *s)
+{
+ return SIZE (s); /* no-brainer on DOS! */
+}
diff --git a/imap/src/osdep/nt/os_nt.c b/imap/src/osdep/nt/os_nt.c
new file mode 100644
index 00000000..0f6fea89
--- /dev/null
+++ b/imap/src/osdep/nt/os_nt.c
@@ -0,0 +1,48 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- NT version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_nt.h" /* must be before osdep includes tcp.h */
+#undef ERROR /* quell conflicting def warning */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <sys\timeb.h>
+#include <fcntl.h>
+#include <sys\stat.h>
+#include "misc.h"
+#include "mailfile.h"
+
+#include "fs_nt.c"
+#include "ftl_nt.c"
+#include "nl_nt.c"
+#include "yunchan.c"
+#include "tcp_nt.c" /* must be before env_nt.c */
+#include "env_nt.c"
+#include "ssl_nt.c"
diff --git a/imap/src/osdep/nt/os_nt.h b/imap/src/osdep/nt/os_nt.h
new file mode 100644
index 00000000..650bb782
--- /dev/null
+++ b/imap/src/osdep/nt/os_nt.h
@@ -0,0 +1,60 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- NT version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 27 April 2007
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys\types.h>
+#include <time.h>
+#include <io.h>
+#include <conio.h>
+#include <process.h>
+#undef ERROR /* quell conflicting defintion warning */
+#include <windows.h>
+#include <lm.h>
+#undef ERROR
+#define ERROR (long) 2 /* must match mail.h */
+
+#if _MSC_VER >= 1400
+#define strtok_r strtok_s /* for some reason they called it this */
+#else
+/* strtok() is actually MT-safe in MSVC. Why is it that Microsoft can do
+ * their CRT right, but GNU, Sun, etc. can't?
+ */
+#define strtok_r(a,b,c) strtok(*(c) = a,b)
+#endif
+
+#include "env_nt.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "yunchan.h"
+
+#undef noErr
+#undef MAC
diff --git a/imap/src/osdep/nt/os_ntk.c b/imap/src/osdep/nt/os_ntk.c
new file mode 100644
index 00000000..87c69123
--- /dev/null
+++ b/imap/src/osdep/nt/os_ntk.c
@@ -0,0 +1,51 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- NT version + Kerberos
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_nt.h" /* must be before osdep includes tcp.h */
+#undef ERROR /* quell conflicting def warning */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <sys\timeb.h>
+#include <fcntl.h>
+#include <sys\stat.h>
+#include "misc.h"
+#include "mailfile.h"
+
+#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
+
+#include "fs_nt.c"
+#include "ftl_nt.c"
+#include "nl_nt.c"
+#include "yunchan.c"
+#include "kerb_mit.c"
+#include "tcp_nt.c" /* must be before env_nt.c */
+#include "env_nt.c"
+#include "ssl_nt.c"
diff --git a/imap/src/osdep/nt/os_old.c b/imap/src/osdep/nt/os_old.c
new file mode 100644
index 00000000..5cd6c0c2
--- /dev/null
+++ b/imap/src/osdep/nt/os_old.c
@@ -0,0 +1,48 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- NT version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 21 December 2007
+ */
+
+#include "tcp_nt.h" /* must be before osdep includes tcp.h */
+#undef ERROR /* quell conflicting def warning */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <sys\timeb.h>
+#include <fcntl.h>
+#include <sys\stat.h>
+#include "misc.h"
+#include "mailfile.h"
+
+#include "fs_nt.c"
+#include "ftl_nt.c"
+#include "nl_nt.c"
+#include "yunchan.c"
+#include "tcp_nt.c" /* must be before env_nt.c */
+#include "env_nt.c"
+#include "ssl_old.c"
diff --git a/imap/src/osdep/nt/os_w2k.c b/imap/src/osdep/nt/os_w2k.c
new file mode 100644
index 00000000..c7af4faa
--- /dev/null
+++ b/imap/src/osdep/nt/os_w2k.c
@@ -0,0 +1,49 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Windows 2000 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_nt.h" /* must be before osdep includes tcp.h */
+#undef ERROR /* quell conflicting def warning */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <sys\timeb.h>
+#include <fcntl.h>
+#include <sys\stat.h>
+#include "misc.h"
+#include "mailfile.h"
+
+#include "fs_nt.c"
+#include "ftl_nt.c"
+#include "nl_nt.c"
+#include "yunchan.c"
+#include "kerb_w2k.c"
+#include "tcp_nt.c" /* must be before env_nt.c */
+#include "env_nt.c"
+#include "ssl_w2k.c"
diff --git a/imap/src/osdep/nt/pmatch.c b/imap/src/osdep/nt/pmatch.c
new file mode 100644
index 00000000..95a0bb86
--- /dev/null
+++ b/imap/src/osdep/nt/pmatch.c
@@ -0,0 +1,89 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: IMAP Wildcard Matching Routines (case-independent)
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 June 2000
+ * Last Edited: 30 August 2006
+ */
+
+/* Wildcard pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if pattern matches base, else NIL
+ */
+
+long pmatch_full (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ /* % at end, OK if no inferiors */
+ if (!pat[1]) return (delim && strchr (s,delim)) ? NIL : T;
+ /* scan remainder of string until delimiter */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while ((*s != delim) && *s++);
+ break;
+ case '*': /* match 0 or more characters */
+ if (!pat[1]) return T; /* * at end, unconditional match */
+ /* scan remainder of string */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while (*s++);
+ break;
+ case '\0': /* end of pattern */
+ return *s ? NIL : T; /* success if also end of base */
+ default: /* match this character */
+ return compare_uchar (*pat,*s) ? NIL : pmatch_full (s+1,pat+1,delim);
+ }
+ return NIL;
+}
+
+/* Directory pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if base is a matching directory of pattern, else NIL
+ */
+
+long dmatch (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ if (!*s) return T; /* end of base means have a subset match */
+ if (!*++pat) return NIL; /* % at end, no inferiors permitted */
+ /* scan remainder of string until delimiter */
+ do if (dmatch (s,pat,delim)) return T;
+ while ((*s != delim) && *s++);
+ if (*s && !s[1]) return T; /* ends with delimiter, must be subset */
+ return dmatch (s,pat,delim);/* do new scan */
+ case '*': /* match 0 or more characters */
+ return T; /* unconditional match */
+ case '\0': /* end of pattern */
+ break;
+ default: /* match this character */
+ if (*s) return compare_uchar (*pat,*s) ? NIL : dmatch (s+1,pat+1,delim);
+ /* end of base, return if at delimiter */
+ else if (*pat == delim) return T;
+ break;
+ }
+ return NIL;
+}
diff --git a/imap/src/osdep/nt/pseudo.c b/imap/src/osdep/nt/pseudo.c
new file mode 100644
index 00000000..1aae8a53
--- /dev/null
+++ b/imap/src/osdep/nt/pseudo.c
@@ -0,0 +1,36 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Pseudo Header Strings
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 September 1996
+ * Last Edited: 30 August 2006
+ */
+
+/* Local sites may wish to alter this text */
+
+char *pseudo_from = "MAILER-DAEMON";
+char *pseudo_name = "Mail System Internal Data";
+char *pseudo_subject = "DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA";
+char *pseudo_msg =
+ "This text is part of the internal format of your mail folder, and is not\na real message. It is created automatically by the mail system software.\nIf deleted, important folder data will be lost, and it will be re-created\nwith the data reset to initial values."
+ ;
diff --git a/imap/src/osdep/nt/pseudo.h b/imap/src/osdep/nt/pseudo.h
new file mode 100644
index 00000000..c9c07628
--- /dev/null
+++ b/imap/src/osdep/nt/pseudo.h
@@ -0,0 +1,30 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Pseudo Header Strings
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 September 1996
+ * Last Edited: 30 August 2006
+ */
+
+
+extern char *pseudo_from,*pseudo_name,*pseudo_subject,*pseudo_msg;
diff --git a/imap/src/osdep/nt/setproto.bat b/imap/src/osdep/nt/setproto.bat
new file mode 100755
index 00000000..ce7cb1ef
--- /dev/null
+++ b/imap/src/osdep/nt/setproto.bat
@@ -0,0 +1,29 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Set default prototype for DOS/NT
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 9 October 1995
+REM Last Edited: 30 August 2006
+
+REM Set the default drivers
+ECHO #define CREATEPROTO %1proto >> LINKAGE.H
+ECHO #define APPENDPROTO %2proto >> LINKAGE.H
diff --git a/imap/src/osdep/nt/ssl_none.c b/imap/src/osdep/nt/ssl_none.c
new file mode 100644
index 00000000..e4dedda7
--- /dev/null
+++ b/imap/src/osdep/nt/ssl_none.c
@@ -0,0 +1,141 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy (no SSL) authentication/encryption module
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 7 February 2001
+ * Last Edited: 30 August 2006
+ */
+
+/* Init server for SSL
+ * Accepts: server name
+ */
+
+void ssl_server_init (char *server)
+{
+ syslog (LOG_ERR,"This server does not support SSL");
+ exit (1); /* punt this program too */
+}
+
+
+/* Start TLS
+ * Accepts: /etc/services service name
+ * Returns: cpystr'd error string if TLS failed, else NIL for success
+ */
+
+char *ssl_start_tls (char *server)
+{
+ return cpystr ("This server does not support TLS");
+}
+
+/* Get character
+ * Returns: character or EOF
+ */
+
+int PBIN (void)
+{
+ return getchar ();
+}
+
+
+/* Get string
+ * Accepts: destination string pointer
+ * number of bytes available
+ * Returns: destination string pointer or NIL if EOF
+ */
+
+char *PSIN (char *s,int n)
+{
+ return fgets (s,n,stdin);
+}
+
+
+/* Get record
+ * Accepts: destination string pointer
+ * number of bytes to read
+ * Returns: T if success, NIL otherwise
+ */
+
+long PSINR (char *s,unsigned long n)
+{
+ unsigned long i;
+ while (n && ((i = fread (s,1,n,stdin)) || (errno == EINTR))) s += i,n -= i;
+ return n ? NIL : LONGT;
+}
+
+
+/* Wait for input
+ * Accepts: timeout in seconds
+ * Returns: T if have input on stdin, else NIL
+ */
+
+long INWAIT (long seconds)
+{
+ return server_input_wait (seconds);
+}
+
+/* Put character
+ * Accepts: character
+ * Returns: character written or EOF
+ */
+
+int PBOUT (int c)
+{
+ return putchar (c);
+}
+
+
+/* Put string
+ * Accepts: source string pointer
+ * Returns: 0 or EOF if error
+ */
+
+int PSOUT (char *s)
+{
+ return fputs (s,stdout);
+}
+
+
+/* Put record
+ * Accepts: source sized text
+ * Returns: 0 or EOF if error
+ */
+
+int PSOUTR (SIZEDTEXT *s)
+{
+ unsigned char *t;
+ unsigned long i,j;
+ for (t = s->data,i = s->size;
+ (i && ((j = fwrite (t,1,i,stdout)) || (errno == EINTR)));
+ t += j,i -= j);
+ return i ? EOF : NIL;
+}
+
+
+/* Flush output
+ * Returns: 0 or EOF if error
+ */
+
+int PFLUSH (void)
+{
+ return fflush (stdout);
+}
diff --git a/imap/src/osdep/nt/ssl_nt.c b/imap/src/osdep/nt/ssl_nt.c
new file mode 100644
index 00000000..c7efa486
--- /dev/null
+++ b/imap/src/osdep/nt/ssl_nt.c
@@ -0,0 +1,721 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: SSL authentication/encryption module for Windows 9x and NT
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 22 September 1998
+ * Last Edited: 13 January 2008
+ */
+
+#define SECURITY_WIN32
+#include <sspi.h>
+#include <schannel.h>
+
+
+#define SSLBUFLEN 8192
+
+
+/* SSL I/O stream */
+
+typedef struct ssl_stream {
+ TCPSTREAM *tcpstream; /* TCP stream */
+ CredHandle cred; /* SSL credentials */
+ CtxtHandle context; /* SSL context */
+ /* stream encryption sizes */
+ SecPkgContext_StreamSizes sizes;
+ size_t bufsize;
+ int ictr; /* input counter */
+ char *iptr; /* input pointer */
+ int iextractr; /* extra input counter */
+ char *iextraptr; /* extra input pointer */
+ char *ibuf; /* input buffer */
+ char *obuf; /* output buffer */
+} SSLSTREAM;
+
+#include "sslio.h"
+
+
+/* Function prototypes */
+
+static SSLSTREAM *ssl_start(TCPSTREAM *tstream,char *host,unsigned long flags);
+static char *ssl_analyze_status (SECURITY_STATUS err,char *buf);
+static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
+ long *contd);
+static long ssl_abort (SSLSTREAM *stream);
+
+/* Secure Sockets Layer network driver dispatch */
+
+static struct ssl_driver ssldriver = {
+ ssl_open, /* open connection */
+ ssl_aopen, /* open preauthenticated connection */
+ ssl_getline, /* get a line */
+ ssl_getbuffer, /* get a buffer */
+ ssl_soutr, /* output pushed data */
+ ssl_sout, /* output string */
+ ssl_close, /* close connection */
+ ssl_host, /* return host name */
+ ssl_remotehost, /* return remote host name */
+ ssl_port, /* return port number */
+ ssl_localhost /* return local host name */
+};
+
+ /* security function table */
+static SecurityFunctionTable *sft = NIL;
+static unsigned long ssltsz = 0;/* SSL maximum token length */
+
+
+/* Define crypt32.dll stuff here in case a pre-IE5 Win9x system */
+
+typedef DWORD (CALLBACK *CNTS) (DWORD,PCERT_NAME_BLOB,DWORD,LPSTR,DWORD);
+typedef BOOL (CALLBACK *CGCC) (HCERTCHAINENGINE,PCCERT_CONTEXT,LPFILETIME,
+ HCERTSTORE,PCERT_CHAIN_PARA,DWORD,LPVOID,
+ PCCERT_CHAIN_CONTEXT *);
+typedef BOOL (CALLBACK *CVCCP) (LPCSTR,PCCERT_CHAIN_CONTEXT,
+ PCERT_CHAIN_POLICY_PARA,
+ PCERT_CHAIN_POLICY_STATUS);
+typedef VOID (CALLBACK *CFCC) (PCCERT_CHAIN_CONTEXT);
+typedef BOOL (CALLBACK *CFCCX) (PCCERT_CONTEXT);
+
+static CNTS certNameToStr = NIL;
+static CGCC certGetCertificateChain = NIL;
+static CVCCP certVerifyCertificateChainPolicy = NIL;
+static CFCC certFreeCertificateChain = NIL;
+static CFCCX certFreeCertificateContext = NIL;
+
+/* One-time SSL initialization */
+
+static int sslonceonly = 0;
+
+void ssl_onceonlyinit (void)
+{
+ if (!sslonceonly++) { /* only need to call it once */
+ HINSTANCE lib;
+ FARPROC pi;
+ ULONG np;
+ SecPkgInfo *pp;
+ int i;
+ /* get security library */
+ if (((lib = LoadLibrary ("schannel.dll")) ||
+ (lib = LoadLibrary ("security.dll"))) &&
+ (pi = GetProcAddress (lib,SECURITY_ENTRYPOINT)) &&
+ (sft = (SecurityFunctionTable *) pi ()) &&
+ !(sft->EnumerateSecurityPackages (&np,&pp))) {
+ /* look for an SSL package */
+ for (i = 0; (i < (int) np); i++) if (!strcmp (pp[i].Name,UNISP_NAME)) {
+ /* note maximum token size and name */
+ ssltsz = pp[i].cbMaxToken;
+ /* apply runtime linkage */
+ mail_parameters (NIL,SET_SSLDRIVER,(void *) &ssldriver);
+ mail_parameters (NIL,SET_SSLSTART,(void *) ssl_start);
+ if ((lib = LoadLibrary ("crypt32.dll")) &&
+ (certGetCertificateChain = (CGCC)
+ GetProcAddress (lib,"CertGetCertificateChain")) &&
+ (certVerifyCertificateChainPolicy = (CVCCP)
+ GetProcAddress (lib,"CertVerifyCertificateChainPolicy")) &&
+ (certFreeCertificateChain = (CFCC)
+ GetProcAddress (lib,"CertFreeCertificateChain")) &&
+ (certFreeCertificateContext = (CFCCX)
+ GetProcAddress (lib,"CertFreeCertificateContext")))
+ certNameToStr = (CNTS) GetProcAddress (lib,"CertNameToStrA");
+ return; /* all done */
+ }
+ }
+ }
+}
+
+/* SSL open
+ * Accepts: host name
+ * contact service name
+ * contact port number
+ * Returns: SSL stream if success else NIL
+ */
+
+SSLSTREAM *ssl_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = tcp_open (host,service,port);
+ return stream ? ssl_start (stream,host,port) : NIL;
+}
+
+
+/* SSL authenticated open
+ * Accepts: host name
+ * service name
+ * returned user name buffer
+ * Returns: SSL stream if success else NIL
+ */
+
+SSLSTREAM *ssl_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL; /* don't use this mechanism with SSL */
+}
+
+/* Start SSL/TLS negotiations
+ * Accepts: open TCP stream of session
+ * user's host name
+ * flags
+ * Returns: SSL stream if success else NIL
+ */
+
+static SSLSTREAM *ssl_start (TCPSTREAM *tstream,char *host,unsigned long flags)
+{
+ SECURITY_STATUS e;
+ ULONG a;
+ TimeStamp t;
+ SecBuffer ibuf[2],obuf[1];
+ SecBufferDesc ibufs,obufs;
+ SCHANNEL_CRED tlscred;
+ CERT_CONTEXT *cert = NIL;
+ CERT_CHAIN_PARA chparam;
+ CERT_CHAIN_CONTEXT *chain;
+ SSL_EXTRA_CERT_CHAIN_POLICY_PARA policy;
+ CERT_CHAIN_POLICY_PARA polparam;
+ CERT_CHAIN_POLICY_STATUS status;
+ char tmp[MAILTMPLEN],certname[256];
+ char *reason = NIL;
+ ULONG req = ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT |
+ ISC_REQ_CONFIDENTIALITY | ISC_REQ_USE_SESSION_KEY |
+ ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM | ISC_REQ_EXTENDED_ERROR |
+ ISC_REQ_MANUAL_CRED_VALIDATION;
+ LPSTR usage[] = {
+ szOID_PKIX_KP_SERVER_AUTH,
+ szOID_SERVER_GATED_CRYPTO,
+ szOID_SGC_NETSCAPE
+ };
+ PWSTR whost = NIL;
+ char *buf = (char *) fs_get (ssltsz);
+ unsigned long size = 0;
+ sslcertificatequery_t scq =
+ (sslcertificatequery_t) mail_parameters (NIL,GET_SSLCERTIFICATEQUERY,NIL);
+ sslfailure_t sf = (sslfailure_t) mail_parameters (NIL,GET_SSLFAILURE,NIL);
+ SSLSTREAM *stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
+ sizeof (SSLSTREAM));
+ stream->tcpstream = tstream; /* bind TCP stream */
+ /* initialize TLS credential */
+ memset (&tlscred,0,sizeof (SCHANNEL_CRED));
+ tlscred.dwVersion = SCHANNEL_CRED_VERSION;
+ tlscred.grbitEnabledProtocols = SP_PROT_TLS1;
+
+ /* acquire credentials */
+ if (sft->AcquireCredentialsHandle
+ (NIL,UNISP_NAME,SECPKG_CRED_OUTBOUND,NIL,(flags & NET_TLSCLIENT) ?
+ &tlscred : NIL,NIL,NIL,&stream->cred,&t)
+ != SEC_E_OK) reason = "Acquire credentials handle failed";
+ else while (!reason) { /* negotiate security context */
+ /* initialize buffers */
+ ibuf[0].cbBuffer = size; ibuf[0].pvBuffer = buf;
+ ibuf[1].cbBuffer = 0; ibuf[1].pvBuffer = NIL;
+ obuf[0].cbBuffer = 0; obuf[0].pvBuffer = NIL;
+ ibuf[0].BufferType = obuf[0].BufferType = SECBUFFER_TOKEN;
+ ibuf[1].BufferType = SECBUFFER_EMPTY;
+ /* initialize buffer descriptors */
+ ibufs.ulVersion = obufs.ulVersion = SECBUFFER_VERSION;
+ ibufs.cBuffers = 2; obufs.cBuffers = 1;
+ ibufs.pBuffers = ibuf; obufs.pBuffers = obuf;
+ /* negotiate security */
+ e = sft->InitializeSecurityContext
+ (&stream->cred,size ? &stream->context : NIL,host,req,0,
+ SECURITY_NETWORK_DREP,size? &ibufs:NIL,0,&stream->context,&obufs,&a,&t);
+ /* have an output buffer we need to send? */
+ if (obuf[0].pvBuffer && obuf[0].cbBuffer) {
+ if (!tcp_sout (stream->tcpstream,obuf[0].pvBuffer,obuf[0].cbBuffer))
+ reason = "Unexpected TCP output disconnect";
+ /* free the buffer */
+ sft->FreeContextBuffer (obuf[0].pvBuffer);
+ }
+ if (!reason) switch (e) { /* negotiation state */
+ case SEC_I_INCOMPLETE_CREDENTIALS:
+ break; /* server wants client auth */
+ case SEC_I_CONTINUE_NEEDED:
+ if (size) { /* continue, read any data? */
+ /* yes, anything regurgiated back to us? */
+ if (ibuf[1].BufferType == SECBUFFER_EXTRA) {
+ /* yes, set this as the new data */
+ memmove (buf,buf + size - ibuf[1].cbBuffer,ibuf[1].cbBuffer);
+ size = ibuf[1].cbBuffer;
+ break;
+ }
+ size = 0; /* otherwise, read more stuff from server */
+ }
+ case SEC_E_INCOMPLETE_MESSAGE:
+ /* need to read more data from server */
+ if (!tcp_getdata (stream->tcpstream))
+ reason = "Unexpected TCP input disconnect";
+ else {
+ memcpy (buf+size,stream->tcpstream->iptr,stream->tcpstream->ictr);
+ size += stream->tcpstream->ictr;
+ /* empty it from TCP's buffers */
+ stream->tcpstream->iptr += stream->tcpstream->ictr;
+ stream->tcpstream->ictr = 0;
+ }
+ break;
+
+ case SEC_E_OK: /* success, any data to be regurgitated? */
+ if (ibuf[1].BufferType == SECBUFFER_EXTRA) {
+ /* yes, set this as the new data */
+ memmove (stream->tcpstream->iptr = stream->tcpstream->ibuf,
+ buf + size - ibuf[1].cbBuffer,ibuf[1].cbBuffer);
+ stream->tcpstream->ictr = ibuf[1].cbBuffer;
+ }
+ if (certNameToStr && !(flags & NET_NOVALIDATECERT)) {
+ /* need validation, make wchar of host */
+ if (!((size = MultiByteToWideChar (CP_ACP,0,host,-1,NIL,0)) &&
+ (whost = (PWSTR) fs_get (size*sizeof (WCHAR))) &&
+ MultiByteToWideChar (CP_ACP,0,host,-1,whost,size)))
+ fatal ("Can't make wchar of host name!");
+ /* get certificate */
+ if ((sft->QueryContextAttributes
+ (&stream->context,SECPKG_ATTR_REMOTE_CERT_CONTEXT,&cert) !=
+ SEC_E_OK) || !cert) {
+ reason = "*Unable to get certificate";
+ strcpy (certname,"<no certificate>");
+ }
+ else { /* get certificate subject name */
+ (*certNameToStr) (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ &cert->pCertInfo->Subject,CERT_X500_NAME_STR,
+ certname,255);
+ /* build certificate chain */
+ memset (&chparam,0,sizeof (chparam));
+ chparam.cbSize = sizeof (chparam);
+ chparam.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
+ chparam.RequestedUsage.Usage.rgpszUsageIdentifier = usage;
+ chparam.RequestedUsage.Usage.cUsageIdentifier =
+ sizeof (usage) / sizeof (LPSTR);
+ if (!(*certGetCertificateChain)
+ (NIL,cert,NIL,cert->hCertStore,&chparam,NIL,NIL,&chain))
+ reason = ssl_analyze_status (GetLastError (),tmp);
+ else { /* validate certificate chain */
+ memset (&policy,0,sizeof (SSL_EXTRA_CERT_CHAIN_POLICY_PARA));
+ policy.cbStruct = sizeof (SSL_EXTRA_CERT_CHAIN_POLICY_PARA);
+ policy.dwAuthType = AUTHTYPE_SERVER;
+ policy.fdwChecks = NIL;
+ policy.pwszServerName = whost;
+ memset (&polparam,0,sizeof (polparam));
+ polparam.cbSize = sizeof (polparam);
+ polparam.pvExtraPolicyPara = &policy;
+ memset (&status,0,sizeof (status));
+ status.cbSize = sizeof (status);
+ if (!(*certVerifyCertificateChainPolicy)
+ (CERT_CHAIN_POLICY_SSL,chain,&polparam,&status))
+ reason = ssl_analyze_status (GetLastError (),tmp);
+ else if (status.dwError)
+ reason = ssl_analyze_status (status.dwError,tmp);
+ (*certFreeCertificateChain) (chain);
+ }
+ (*certFreeCertificateContext) (cert);
+ }
+ if (whost) fs_give ((void **) &whost);
+
+ if (reason) { /* got an error? */
+ /* application callback */
+ if (scq) reason = (*scq) ((*reason == '*') ? reason + 1 : reason,
+ host,certname) ? NIL : "";
+ else if (*certname) { /* error message to return via mm_log() */
+ sprintf (buf,"*%.128s: %.255s",
+ (*reason == '*') ? reason + 1 : reason,certname);
+ reason = buf;
+ }
+ }
+ }
+ if (reason ||
+ (reason = ssl_analyze_status
+ (sft->QueryContextAttributes
+ (&stream->context,SECPKG_ATTR_STREAM_SIZES,&stream->sizes),buf)))
+ break; /* error in certificate or getting sizes */
+ fs_give ((void **) &buf); /* flush temporary buffer */
+ /* make maximum-sized buffers */
+ stream->bufsize = stream->sizes.cbHeader +
+ stream->sizes.cbMaximumMessage + stream->sizes.cbTrailer;
+ if (stream->sizes.cbMaximumMessage < SSLBUFLEN)
+ fatal ("cbMaximumMessage is less than SSLBUFLEN!");
+ else if (stream->sizes.cbMaximumMessage < 16384) {
+ sprintf (tmp,"WINDOWS BUG: cbMaximumMessage = %ld, should be 16384",
+ (long) stream->sizes.cbMaximumMessage);
+ mm_log (tmp,NIL);
+ }
+ stream->ibuf = (char *) fs_get (stream->bufsize);
+ stream->obuf = (char *) fs_get (stream->bufsize);
+ return stream;
+ default:
+ reason = ssl_analyze_status (e,buf);
+ }
+ }
+ ssl_close (stream); /* failed to do SSL */
+ stream = NIL; /* no stream returned */
+ switch (*reason) { /* analyze reason */
+ case '*': /* certificate failure */
+ ++reason; /* skip over certificate failure indication */
+ /* pass to error callback */
+ if (sf) (*sf) (host,reason,flags);
+ else { /* no error callback, build error message */
+ sprintf (tmp,"Certificate failure for %.80s: %.512s",host,reason);
+ mm_log (tmp,ERROR);
+ }
+ case '\0': /* user answered no to certificate callback */
+ if (flags & NET_TRYSSL) /* return dummy stream to stop tryssl */
+ stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
+ sizeof (SSLSTREAM));
+ break;
+ default: /* non-certificate failure */
+ if (flags & NET_TRYSSL); /* no error output if tryssl */
+ /* pass to error callback */
+ else if (sf) (*sf) (host,reason,flags);
+ else { /* no error callback, build error message */
+ sprintf (tmp,"TLS/SSL failure for %.80s: %.512s",host,reason);
+ mm_log (tmp,ERROR);
+ }
+ break;
+ }
+ fs_give ((void **) &buf); /* flush temporary buffer */
+ return stream;
+}
+
+/* Generate error text from SSL error code
+ * Accepts: SSL status
+ * scratch buffer
+ * Returns: text if error status, else NIL
+ */
+
+static char *ssl_analyze_status (SECURITY_STATUS err,char *buf)
+{
+ switch (err) {
+ case SEC_E_OK: /* no error */
+ case SEC_I_CONTINUE_NEEDED:
+ case SEC_I_INCOMPLETE_CREDENTIALS:
+ case SEC_E_INCOMPLETE_MESSAGE:
+ return NIL;
+ case SEC_E_NO_AUTHENTICATING_AUTHORITY:
+ mm_log ("unexpected SEC_E_NO_AUTHENTICATING_AUTHORITY",NIL);
+ return "*No authority could be contacted for authentication";
+ case SEC_E_WRONG_PRINCIPAL:
+ mm_log ("unexpected SEC_E_WRONG_PRINCIPAL",NIL);
+ case CERT_E_CN_NO_MATCH:
+ return "*Server name does not match certificate";
+ case SEC_E_UNTRUSTED_ROOT:
+ mm_log ("unexpected SEC_E_UNTRUSTED_ROOT",NIL);
+ case CERT_E_UNTRUSTEDROOT:
+ return "*Self-signed certificate or untrusted authority";
+ case SEC_E_CERT_EXPIRED:
+ mm_log ("unexpected SEC_E_CERT_EXPIRED",NIL);
+ case CERT_E_EXPIRED:
+ return "*Certificate has expired";
+ case CERT_E_REVOKED:
+ return "*Certificate revoked";
+ case SEC_E_INVALID_TOKEN:
+ return "Invalid token, probably not an SSL server";
+ case SEC_E_UNSUPPORTED_FUNCTION:
+ return "SSL not supported on this machine - upgrade your system software";
+ }
+ sprintf (buf,"Unexpected SSPI or certificate error %lx - report this",err);
+ return buf;
+}
+
+/* SSL receive line
+ * Accepts: SSL stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *ssl_getline (SSLSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = ssl_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = ssl_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* SSL receive line or partial line
+ * Accepts: SSL stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!ssl_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!ssl_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* SSL receive buffer
+ * Accepts: SSL stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long ssl_getbuffer (SSLSTREAM *stream,unsigned long size,char *buffer)
+{
+ unsigned long n;
+ while (size > 0) { /* until request satisfied */
+ if (!ssl_getdata (stream)) return NIL;
+ n = min (size,stream->ictr);/* number of bytes to transfer */
+ /* do the copy */
+ memcpy (buffer,stream->iptr,n);
+ buffer += n; /* update pointer */
+ stream->iptr += n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -= n;
+ }
+ buffer[0] = '\0'; /* tie off string */
+ return T;
+}
+
+/* SSL receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long ssl_getdata (SSLSTREAM *stream)
+{
+ while (stream->ictr < 1) { /* decrypted buffer empty? */
+ SECURITY_STATUS status;
+ SecBuffer buf[4];
+ SecBufferDesc msg;
+ size_t i;
+ size_t n = 0; /* initially no bytes to decrypt */
+ do { /* yes, make sure have data from TCP */
+ if (stream->iextractr) { /* have previous unread data? */
+ memcpy (stream->ibuf + n,stream->iextraptr,stream->iextractr);
+ n += stream->iextractr; /* update number of bytes read */
+ stream->iextractr = 0; /* no more extra data */
+ }
+ else { /* read from TCP */
+ if (!tcp_getdata (stream->tcpstream)) return ssl_abort (stream);
+ /* maximum amount of data to copy */
+ if (!(i = min (stream->bufsize - n,stream->tcpstream->ictr)))
+ fatal ("incomplete SecBuffer exceeds maximum buffer size");
+ /* do the copy */
+ memcpy (stream->ibuf + n,stream->tcpstream->iptr,i);
+ stream->tcpstream->iptr += i;
+ stream->tcpstream->ictr -= i;
+ n += i; /* update number of bytes to decrypt */
+ }
+ buf[0].cbBuffer = n; /* first SecBuffer gets data */
+ buf[0].pvBuffer = stream->ibuf;
+ buf[0].BufferType = SECBUFFER_DATA;
+ /* subsequent ones are for spares */
+ buf[1].BufferType = buf[2].BufferType = buf[3].BufferType =
+ SECBUFFER_EMPTY;
+ msg.ulVersion = SECBUFFER_VERSION;
+ msg.cBuffers = 4; /* number of SecBuffers */
+ msg.pBuffers = buf; /* first SecBuffer */
+
+ } while ((status = ((DECRYPT_MESSAGE_FN) sft->Reserved4)
+ (&stream->context,&msg,0,NIL)) == SEC_E_INCOMPLETE_MESSAGE);
+ switch (status) {
+ case SEC_E_OK: /* won */
+ case SEC_I_RENEGOTIATE: /* won but lost it after this buffer */
+ /* hunt for a buffer */
+ for (i = 0; (i < 4) && (buf[i].BufferType != SECBUFFER_DATA) ; i++);
+ if (i < 4) { /* found a buffer? */
+ /* yes, set up pointer and counter */
+ stream->iptr = buf[i].pvBuffer;
+ stream->ictr = buf[i].cbBuffer;
+ /* any unprocessed data? */
+ while (++i < 4) if (buf[i].BufferType == SECBUFFER_EXTRA) {
+ /* yes, note for next time around */
+ stream->iextraptr = buf[i].pvBuffer;
+ stream->iextractr = buf[i].cbBuffer;
+ }
+ }
+ break;
+ default: /* anything else means we've lost */
+ return ssl_abort (stream);
+ }
+ }
+ return LONGT;
+}
+
+/* SSL send string as record
+ * Accepts: SSL stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long ssl_soutr (SSLSTREAM *stream,char *string)
+{
+ return ssl_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* SSL send string
+ * Accepts: SSL stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long ssl_sout (SSLSTREAM *stream,char *string,unsigned long size)
+{
+ SecBuffer buf[4];
+ SecBufferDesc msg;
+ char *s;
+ size_t n;
+ if (!stream->tcpstream) return NIL;
+ /* until request satisfied */
+ for (s = stream->ibuf,n = 0; size;) {
+ /* header */
+ buf[0].BufferType = SECBUFFER_STREAM_HEADER;
+ memset (buf[0].pvBuffer = stream->obuf,0,
+ buf[0].cbBuffer = stream->sizes.cbHeader);
+ /* message (up to maximum size) */
+ buf[1].BufferType = SECBUFFER_DATA;
+ memcpy (buf[1].pvBuffer = stream->obuf + stream->sizes.cbHeader,string,
+ buf[1].cbBuffer = min (size,SSLBUFLEN));
+ /* trailer */
+ buf[2].BufferType = SECBUFFER_STREAM_TRAILER;
+ memset (buf[2].pvBuffer = ((char *) buf[1].pvBuffer) + buf[1].cbBuffer,0,
+ buf[2].cbBuffer = stream->sizes.cbTrailer);
+ /* spare */
+ buf[3].BufferType = SECBUFFER_EMPTY;
+ msg.ulVersion = SECBUFFER_VERSION;
+ msg.cBuffers = 4; /* number of SecBuffers */
+ msg.pBuffers = buf; /* first SecBuffer */
+ string += buf[1].cbBuffer;
+ size -= buf[1].cbBuffer; /* this many bytes processed */
+ /* encrypt and send message */
+ if ((((ENCRYPT_MESSAGE_FN) sft->Reserved3)
+ (&stream->context,0,&msg,NIL) != SEC_E_OK) ||
+ !tcp_sout (stream->tcpstream,stream->obuf,
+ buf[0].cbBuffer + buf[1].cbBuffer + buf[2].cbBuffer))
+ return ssl_abort (stream);/* encryption or sending failed */
+ }
+ return LONGT;
+}
+
+/* SSL close
+ * Accepts: SSL stream
+ */
+
+void ssl_close (SSLSTREAM *stream)
+{
+ ssl_abort (stream); /* nuke the stream */
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* SSL abort stream
+ * Accepts: SSL stream
+ * Returns: NIL always
+ */
+
+static long ssl_abort (SSLSTREAM *stream)
+{
+ if (stream->tcpstream) { /* close TCP stream */
+ sft->DeleteSecurityContext (&stream->context);
+ sft->FreeCredentialHandle (&stream->cred);
+ tcp_close (stream->tcpstream);
+ stream->tcpstream = NIL;
+ }
+ if (stream->ibuf) fs_give ((void **) &stream->ibuf);
+ if (stream->obuf) fs_give ((void **) &stream->obuf);
+ return NIL;
+}
+
+/* SSL get host name
+ * Accepts: SSL stream
+ * Returns: host name for this stream
+ */
+
+char *ssl_host (SSLSTREAM *stream)
+{
+ return tcp_host (stream->tcpstream);
+}
+
+
+/* SSL get remote host name
+ * Accepts: SSL stream
+ * Returns: host name for this stream
+ */
+
+char *ssl_remotehost (SSLSTREAM *stream)
+{
+ return tcp_remotehost (stream->tcpstream);
+}
+
+
+/* SSL return port for this stream
+ * Accepts: SSL stream
+ * Returns: port number for this stream
+ */
+
+unsigned long ssl_port (SSLSTREAM *stream)
+{
+ return tcp_port (stream->tcpstream);
+}
+
+
+/* SSL get local host name
+ * Accepts: SSL stream
+ * Returns: local host name
+ */
+
+char *ssl_localhost (SSLSTREAM *stream)
+{
+ return tcp_localhost (stream->tcpstream);
+}
+
+#include "ssl_none.c" /* currently no server support */
diff --git a/imap/src/osdep/nt/ssl_old.c b/imap/src/osdep/nt/ssl_old.c
new file mode 100644
index 00000000..121bd02e
--- /dev/null
+++ b/imap/src/osdep/nt/ssl_old.c
@@ -0,0 +1,625 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: SSL authentication/encryption module for Windows 9x and NT
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 22 September 1998
+ * Last Edited: 13 January 2008
+ */
+
+#define SECURITY_WIN32
+#include <sspi.h>
+#if(_WIN32_WINNT < 0x0400)
+typedef unsigned int ALG_ID;
+#else
+#include <wincrypt.h>
+ALGIDDEF
+#endif
+#include <schnlsp.h>
+#include <issperr.h>
+
+ /* in case a binary runs on Windows 2000 */
+#ifndef ISC_REQ_MANUAL_CRED_VALIDATION
+#define ISC_REQ_MANUAL_CRED_VALIDATION 0x00080000
+#endif
+#ifndef SEC_E_UNTRUSTED_ROOT
+#define SEC_E_UNTRUSTED_ROOT ((HRESULT) 0x80090325L)
+#endif
+#ifndef SEC_E_CERT_EXPIRED
+#define SEC_E_CERT_EXPIRED ((HRESULT) 0x80090328L)
+#endif
+
+
+#define SSLBUFLEN 8192
+
+
+/* SSL I/O stream */
+
+typedef struct ssl_stream {
+ TCPSTREAM *tcpstream; /* TCP stream */
+ CredHandle cred; /* SSL credentials */
+ CtxtHandle context; /* SSL context */
+ /* stream encryption sizes */
+ SecPkgContext_StreamSizes sizes;
+ size_t bufsize;
+ int ictr; /* input counter */
+ char *iptr; /* input pointer */
+ int iextractr; /* extra input counter */
+ char *iextraptr; /* extra input pointer */
+ char *ibuf; /* input buffer */
+ char *obuf; /* output buffer */
+} SSLSTREAM;
+
+#include "sslio.h"
+
+
+/* Function prototypes */
+
+static SSLSTREAM *ssl_start(TCPSTREAM *tstream,char *host,unsigned long flags);
+static char *ssl_analyze_status (SECURITY_STATUS err,char *buf);
+static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
+ long *contd);
+static long ssl_abort (SSLSTREAM *stream);
+
+/* Secure Sockets Layer network driver dispatch */
+
+static struct ssl_driver ssldriver = {
+ ssl_open, /* open connection */
+ ssl_aopen, /* open preauthenticated connection */
+ ssl_getline, /* get a line */
+ ssl_getbuffer, /* get a buffer */
+ ssl_soutr, /* output pushed data */
+ ssl_sout, /* output string */
+ ssl_close, /* close connection */
+ ssl_host, /* return host name */
+ ssl_remotehost, /* return remote host name */
+ ssl_port, /* return port number */
+ ssl_localhost /* return local host name */
+};
+
+ /* security function table */
+static SecurityFunctionTable *sft = NIL;
+static unsigned long ssltsz = 0;/* SSL maximum token length */
+
+/* One-time SSL initialization */
+
+static int sslonceonly = 0;
+
+void ssl_onceonlyinit (void)
+{
+ if (!sslonceonly++) { /* only need to call it once */
+ HINSTANCE lib;
+ FARPROC pi;
+ ULONG np;
+ SecPkgInfo *pp;
+ int i;
+ /* get security library */
+ if (((lib = LoadLibrary ("schannel.dll")) ||
+ (lib = LoadLibrary ("security.dll"))) &&
+ (pi = GetProcAddress (lib,SECURITY_ENTRYPOINT)) &&
+ (sft = (SecurityFunctionTable *) pi ()) &&
+ !(sft->EnumerateSecurityPackages (&np,&pp))) {
+ /* look for an SSL package */
+ for (i = 0; (i < (int) np); i++) if (!strcmp (pp[i].Name,UNISP_NAME)) {
+ /* note maximum token size and name */
+ ssltsz = pp[i].cbMaxToken;
+ /* apply runtime linkage */
+ mail_parameters (NIL,SET_SSLDRIVER,(void *) &ssldriver);
+ mail_parameters (NIL,SET_SSLSTART,(void *) ssl_start);
+ return; /* all done */
+ }
+ }
+ }
+}
+
+/* SSL open
+ * Accepts: host name
+ * contact service name
+ * contact port number
+ * Returns: SSL stream if success else NIL
+ */
+
+SSLSTREAM *ssl_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = tcp_open (host,service,port);
+ return stream ? ssl_start (stream,host,port) : NIL;
+}
+
+
+/* SSL authenticated open
+ * Accepts: host name
+ * service name
+ * returned user name buffer
+ * Returns: SSL stream if success else NIL
+ */
+
+SSLSTREAM *ssl_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL; /* don't use this mechanism with SSL */
+}
+
+/* Start SSL/TLS negotiations
+ * Accepts: open TCP stream of session
+ * user's host name
+ * flags
+ * Returns: SSL stream if success else NIL
+ */
+
+static SSLSTREAM *ssl_start (TCPSTREAM *tstream,char *host,unsigned long flags)
+{
+ SECURITY_STATUS e;
+ ULONG a;
+ TimeStamp t;
+ SecBuffer ibuf[2],obuf[1];
+ SecBufferDesc ibufs,obufs;
+ char tmp[MAILTMPLEN];
+ char *reason = NIL;
+ ULONG req = ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT |
+ ISC_REQ_CONFIDENTIALITY | ISC_REQ_USE_SESSION_KEY |
+ ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM | ISC_REQ_EXTENDED_ERROR +
+ ((flags & NET_NOVALIDATECERT) ? ISC_REQ_MANUAL_CRED_VALIDATION :
+ ISC_REQ_MUTUAL_AUTH);
+ SCHANNEL_CRED tlscred;
+ char *buf = (char *) fs_get (ssltsz);
+ unsigned long size = 0;
+ sslfailure_t sf = (sslfailure_t) mail_parameters (NIL,GET_SSLFAILURE,NIL);
+ SSLSTREAM *stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
+ sizeof (SSLSTREAM));
+ stream->tcpstream = tstream; /* bind TCP stream */
+ /* initialize TLS credential */
+ memset (&tlscred,0,sizeof (SCHANNEL_CRED));
+ tlscred.dwVersion = SCHANNEL_CRED_VERSION;
+ tlscred.grbitEnabledProtocols = SP_PROT_TLS1;
+
+ /* acquire credentials */
+ if (sft->AcquireCredentialsHandle
+ (NIL,UNISP_NAME,SECPKG_CRED_OUTBOUND,NIL,(flags & NET_TLSCLIENT) ?
+ &tlscred : NIL,NIL,NIL,&stream->cred,&t)
+ != SEC_E_OK) reason = "Acquire credentials handle failed";
+ else while (!reason) { /* negotiate security context */
+ /* initialize buffers */
+ ibuf[0].cbBuffer = size; ibuf[0].pvBuffer = buf;
+ ibuf[1].cbBuffer = 0; ibuf[1].pvBuffer = NIL;
+ obuf[0].cbBuffer = 0; obuf[0].pvBuffer = NIL;
+ ibuf[0].BufferType = obuf[0].BufferType = SECBUFFER_TOKEN;
+ ibuf[1].BufferType = SECBUFFER_EMPTY;
+ /* initialize buffer descriptors */
+ ibufs.ulVersion = obufs.ulVersion = SECBUFFER_VERSION;
+ ibufs.cBuffers = 2; obufs.cBuffers = 1;
+ ibufs.pBuffers = ibuf; obufs.pBuffers = obuf;
+ /* negotiate security */
+ e = sft->InitializeSecurityContext
+ (&stream->cred,size ? &stream->context : NIL,host,req,0,
+ SECURITY_NETWORK_DREP,size? &ibufs:NIL,0,&stream->context,&obufs,&a,&t);
+ /* have an output buffer we need to send? */
+ if (obuf[0].pvBuffer && obuf[0].cbBuffer) {
+ if (!tcp_sout (stream->tcpstream,obuf[0].pvBuffer,obuf[0].cbBuffer))
+ reason = "Unexpected TCP output disconnect";
+ /* free the buffer */
+ sft->FreeContextBuffer (obuf[0].pvBuffer);
+ }
+ if (!reason) switch (e) { /* negotiation state */
+ case SEC_I_INCOMPLETE_CREDENTIALS:
+ break; /* server wants client auth */
+ case SEC_I_CONTINUE_NEEDED:
+ if (size) { /* continue, read any data? */
+ /* yes, anything regurgiated back to us? */
+ if (ibuf[1].BufferType == SECBUFFER_EXTRA) {
+ /* yes, set this as the new data */
+ memmove (buf,buf + size - ibuf[1].cbBuffer,ibuf[1].cbBuffer);
+ size = ibuf[1].cbBuffer;
+ break;
+ }
+ size = 0; /* otherwise, read more stuff from server */
+ }
+ case SEC_E_INCOMPLETE_MESSAGE:
+ /* need to read more data from server */
+ if (!tcp_getdata (stream->tcpstream))
+ reason = "Unexpected TCP input disconnect";
+ else {
+ memcpy (buf+size,stream->tcpstream->iptr,stream->tcpstream->ictr);
+ size += stream->tcpstream->ictr;
+ /* empty it from TCP's buffers */
+ stream->tcpstream->iptr += stream->tcpstream->ictr;
+ stream->tcpstream->ictr = 0;
+ }
+ break;
+
+ case SEC_E_OK: /* success, any data to be regurgitated? */
+ if (ibuf[1].BufferType == SECBUFFER_EXTRA) {
+ /* yes, set this as the new data */
+ memmove (stream->tcpstream->iptr = stream->tcpstream->ibuf,
+ buf + size - ibuf[1].cbBuffer,ibuf[1].cbBuffer);
+ stream->tcpstream->ictr = ibuf[1].cbBuffer;
+ }
+ if (reason = ssl_analyze_status
+ (sft->QueryContextAttributes
+ (&stream->context,SECPKG_ATTR_STREAM_SIZES,&stream->sizes),buf))
+ break; /* error getting sizes */
+ fs_give ((void **) &buf); /* flush temporary buffer */
+ /* make maximum-sized buffers */
+ stream->bufsize = stream->sizes.cbHeader +
+ stream->sizes.cbMaximumMessage + stream->sizes.cbTrailer;
+ if (stream->sizes.cbMaximumMessage < SSLBUFLEN)
+ fatal ("cbMaximumMessage is less than SSLBUFLEN!");
+ else if (stream->sizes.cbMaximumMessage < 16384) {
+ sprintf (tmp,"WINDOWS BUG: cbMaximumMessage = %ld, should be 16384",
+ (long) stream->sizes.cbMaximumMessage);
+ mm_log (tmp,NIL);
+ }
+ stream->ibuf = (char *) fs_get (stream->bufsize);
+ stream->obuf = (char *) fs_get (stream->bufsize);
+ return stream;
+ default:
+ reason = ssl_analyze_status (e,buf);
+ }
+ }
+ ssl_close (stream); /* failed to do SSL */
+ stream = NIL; /* no stream returned */
+ fs_give ((void **) &buf); /* flush temporary buffer */
+ switch (*reason) { /* analyze reason */
+ case '*': /* certificate failure */
+ ++reason; /* skip over certificate failure indication */
+ /* pass to error callback */
+ if (sf) (*sf) (host,reason,flags);
+ else { /* no error callback, build error message */
+ sprintf (tmp,"Certificate failure for %.80s: %.512s",host,reason);
+ mm_log (tmp,ERROR);
+ }
+ case '\0': /* user answered no to certificate callback */
+ if (flags & NET_TRYSSL) /* return dummy stream to stop tryssl */
+ stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
+ sizeof (SSLSTREAM));
+ break;
+ default: /* non-certificate failure */
+ if (flags & NET_TRYSSL); /* no error output if tryssl */
+ /* pass to error callback */
+ else if (sf) (*sf) (host,reason,flags);
+ else { /* no error callback, build error message */
+ sprintf (tmp,"TLS/SSL failure for %.80s: %.512s",host,reason);
+ mm_log (tmp,ERROR);
+ }
+ break;
+ }
+ return stream;
+}
+
+/* Generate error text from SSL error code
+ * Accepts: SSL status
+ * scratch buffer
+ * Returns: text if error status, else NIL
+ */
+
+static char *ssl_analyze_status (SECURITY_STATUS err,char *buf)
+{
+ switch (err) {
+ case SEC_E_OK: /* no error */
+ case SEC_I_CONTINUE_NEEDED:
+ case SEC_I_INCOMPLETE_CREDENTIALS:
+ case SEC_E_INCOMPLETE_MESSAGE:
+ return NIL;
+ case SEC_E_NO_AUTHENTICATING_AUTHORITY:
+ return "*No authority could be contacted for authentication";
+ case SEC_E_WRONG_PRINCIPAL:
+ return "*Server name does not match certificate";
+ case SEC_E_UNTRUSTED_ROOT:
+ return "*Self-signed certificate or untrusted authority";
+ case SEC_E_CERT_EXPIRED:
+ return "*Certificate has expired";
+ case SEC_E_INVALID_TOKEN:
+ return "Invalid token, probably not an SSL server";
+ case SEC_E_UNSUPPORTED_FUNCTION:
+ return "SSL not supported on this machine - upgrade your system software";
+ }
+ sprintf (buf,"Unexpected SChannel error %lx - report this",err);
+ return buf;
+}
+
+/* SSL receive line
+ * Accepts: SSL stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *ssl_getline (SSLSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = ssl_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = ssl_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* SSL receive line or partial line
+ * Accepts: SSL stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!ssl_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!ssl_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* SSL receive buffer
+ * Accepts: SSL stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long ssl_getbuffer (SSLSTREAM *stream,unsigned long size,char *buffer)
+{
+ unsigned long n;
+ while (size > 0) { /* until request satisfied */
+ if (!ssl_getdata (stream)) return NIL;
+ n = min (size,stream->ictr);/* number of bytes to transfer */
+ /* do the copy */
+ memcpy (buffer,stream->iptr,n);
+ buffer += n; /* update pointer */
+ stream->iptr += n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -= n;
+ }
+ buffer[0] = '\0'; /* tie off string */
+ return T;
+}
+
+/* SSL receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long ssl_getdata (SSLSTREAM *stream)
+{
+ while (stream->ictr < 1) { /* decrypted buffer empty? */
+ SECURITY_STATUS status;
+ SecBuffer buf[4];
+ SecBufferDesc msg;
+ size_t i;
+ size_t n = 0; /* initially no bytes to decrypt */
+ do { /* yes, make sure have data from TCP */
+ if (stream->iextractr) { /* have previous unread data? */
+ memcpy (stream->ibuf + n,stream->iextraptr,stream->iextractr);
+ n += stream->iextractr; /* update number of bytes read */
+ stream->iextractr = 0; /* no more extra data */
+ }
+ else { /* read from TCP */
+ if (!tcp_getdata (stream->tcpstream)) return ssl_abort (stream);
+ /* maximum amount of data to copy */
+ if (!(i = min (stream->bufsize - n,stream->tcpstream->ictr)))
+ fatal ("incomplete SecBuffer exceeds maximum buffer size");
+ /* do the copy */
+ memcpy (stream->ibuf + n,stream->tcpstream->iptr,i);
+ stream->tcpstream->iptr += i;
+ stream->tcpstream->ictr -= i;
+ n += i; /* update number of bytes to decrypt */
+ }
+ buf[0].cbBuffer = n; /* first SecBuffer gets data */
+ buf[0].pvBuffer = stream->ibuf;
+ buf[0].BufferType = SECBUFFER_DATA;
+ /* subsequent ones are for spares */
+ buf[1].BufferType = buf[2].BufferType = buf[3].BufferType =
+ SECBUFFER_EMPTY;
+ msg.ulVersion = SECBUFFER_VERSION;
+ msg.cBuffers = 4; /* number of SecBuffers */
+ msg.pBuffers = buf; /* first SecBuffer */
+
+ } while ((status = ((DECRYPT_MESSAGE_FN) sft->Reserved4)
+ (&stream->context,&msg,0,NIL)) == SEC_E_INCOMPLETE_MESSAGE);
+ switch (status) {
+ case SEC_E_OK: /* won */
+ case SEC_I_RENEGOTIATE: /* won but lost it after this buffer */
+ /* hunt for a buffer */
+ for (i = 0; (i < 4) && (buf[i].BufferType != SECBUFFER_DATA) ; i++);
+ if (i < 4) { /* found a buffer? */
+ /* yes, set up pointer and counter */
+ stream->iptr = buf[i].pvBuffer;
+ stream->ictr = buf[i].cbBuffer;
+ /* any unprocessed data? */
+ while (++i < 4) if (buf[i].BufferType == SECBUFFER_EXTRA) {
+ /* yes, note for next time around */
+ stream->iextraptr = buf[i].pvBuffer;
+ stream->iextractr = buf[i].cbBuffer;
+ }
+ }
+ break;
+ default: /* anything else means we've lost */
+ return ssl_abort (stream);
+ }
+ }
+ return LONGT;
+}
+
+/* SSL send string as record
+ * Accepts: SSL stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long ssl_soutr (SSLSTREAM *stream,char *string)
+{
+ return ssl_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* SSL send string
+ * Accepts: SSL stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long ssl_sout (SSLSTREAM *stream,char *string,unsigned long size)
+{
+ SecBuffer buf[4];
+ SecBufferDesc msg;
+ char *s = stream->ibuf;
+ size_t n = 0;
+ while (size) { /* until satisfied request */
+ /* header */
+ buf[0].BufferType = SECBUFFER_STREAM_HEADER;
+ memset (buf[0].pvBuffer = stream->obuf,0,
+ buf[0].cbBuffer = stream->sizes.cbHeader);
+ /* message (up to maximum size) */
+ buf[1].BufferType = SECBUFFER_DATA;
+ memcpy (buf[1].pvBuffer = stream->obuf + stream->sizes.cbHeader,string,
+ buf[1].cbBuffer = min (size,SSLBUFLEN));
+ /* trailer */
+ buf[2].BufferType = SECBUFFER_STREAM_TRAILER;
+ memset (buf[2].pvBuffer = ((char *) buf[1].pvBuffer) + buf[1].cbBuffer,0,
+ buf[2].cbBuffer = stream->sizes.cbTrailer);
+ /* spare */
+ buf[3].BufferType = SECBUFFER_EMPTY;
+ msg.ulVersion = SECBUFFER_VERSION;
+ msg.cBuffers = 4; /* number of SecBuffers */
+ msg.pBuffers = buf; /* first SecBuffer */
+ string += buf[1].cbBuffer;
+ size -= buf[1].cbBuffer; /* this many bytes processed */
+ /* encrypt and send message */
+ if ((((ENCRYPT_MESSAGE_FN) sft->Reserved3)
+ (&stream->context,0,&msg,NIL) != SEC_E_OK) ||
+ !tcp_sout (stream->tcpstream,stream->obuf,
+ buf[0].cbBuffer + buf[1].cbBuffer + buf[2].cbBuffer))
+ return ssl_abort (stream);/* encryption or sending failed */
+ }
+ return LONGT;
+}
+
+/* SSL close
+ * Accepts: SSL stream
+ */
+
+void ssl_close (SSLSTREAM *stream)
+{
+ ssl_abort (stream); /* nuke the stream */
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* SSL abort stream
+ * Accepts: SSL stream
+ * Returns: NIL always
+ */
+
+static long ssl_abort (SSLSTREAM *stream)
+{
+ if (stream->tcpstream) { /* close TCP stream */
+ sft->DeleteSecurityContext (&stream->context);
+ sft->FreeCredentialHandle (&stream->cred);
+ tcp_close (stream->tcpstream);
+ stream->tcpstream = NIL;
+ }
+ if (stream->ibuf) fs_give ((void **) &stream->ibuf);
+ if (stream->obuf) fs_give ((void **) &stream->obuf);
+ return NIL;
+}
+
+/* SSL get host name
+ * Accepts: SSL stream
+ * Returns: host name for this stream
+ */
+
+char *ssl_host (SSLSTREAM *stream)
+{
+ return tcp_host (stream->tcpstream);
+}
+
+
+/* SSL get remote host name
+ * Accepts: SSL stream
+ * Returns: host name for this stream
+ */
+
+char *ssl_remotehost (SSLSTREAM *stream)
+{
+ return tcp_remotehost (stream->tcpstream);
+}
+
+
+/* SSL return port for this stream
+ * Accepts: SSL stream
+ * Returns: port number for this stream
+ */
+
+unsigned long ssl_port (SSLSTREAM *stream)
+{
+ return tcp_port (stream->tcpstream);
+}
+
+
+/* SSL get local host name
+ * Accepts: SSL stream
+ * Returns: local host name
+ */
+
+char *ssl_localhost (SSLSTREAM *stream)
+{
+ return tcp_localhost (stream->tcpstream);
+}
+
+#include "ssl_none.c" /* currently no server support */
diff --git a/imap/src/osdep/nt/ssl_w2k.c b/imap/src/osdep/nt/ssl_w2k.c
new file mode 100644
index 00000000..f5d8d1f5
--- /dev/null
+++ b/imap/src/osdep/nt/ssl_w2k.c
@@ -0,0 +1,683 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: SSL authentication/encryption module for Windows 2000
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 22 September 1998
+ * Last Edited: 13 January 2008
+ */
+
+#define SECURITY_WIN32
+#include <sspi.h>
+#include <schannel.h>
+
+
+#define SSLBUFLEN 8192
+
+
+/* SSL I/O stream */
+
+typedef struct ssl_stream {
+ TCPSTREAM *tcpstream; /* TCP stream */
+ CredHandle cred; /* SSL credentials */
+ CtxtHandle context; /* SSL context */
+ /* stream encryption sizes */
+ SecPkgContext_StreamSizes sizes;
+ size_t bufsize;
+ int ictr; /* input counter */
+ char *iptr; /* input pointer */
+ int iextractr; /* extra input counter */
+ char *iextraptr; /* extra input pointer */
+ char *ibuf; /* input buffer */
+ char *obuf; /* output buffer */
+} SSLSTREAM;
+
+#include "sslio.h"
+
+
+/* Function prototypes */
+
+static SSLSTREAM *ssl_start(TCPSTREAM *tstream,char *host,unsigned long flags);
+static char *ssl_analyze_status (SECURITY_STATUS err,char *buf);
+static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
+ long *contd);
+static long ssl_abort (SSLSTREAM *stream);
+
+/* Secure Sockets Layer network driver dispatch */
+
+static struct ssl_driver ssldriver = {
+ ssl_open, /* open connection */
+ ssl_aopen, /* open preauthenticated connection */
+ ssl_getline, /* get a line */
+ ssl_getbuffer, /* get a buffer */
+ ssl_soutr, /* output pushed data */
+ ssl_sout, /* output string */
+ ssl_close, /* close connection */
+ ssl_host, /* return host name */
+ ssl_remotehost, /* return remote host name */
+ ssl_port, /* return port number */
+ ssl_localhost /* return local host name */
+};
+
+static unsigned long ssltsz = 0;/* SSL maximum token length */
+
+/* One-time SSL initialization */
+
+static int sslonceonly = 0;
+
+void ssl_onceonlyinit (void)
+{
+ if (!sslonceonly++) { /* only need to call it once */
+ ULONG np;
+ SecPkgInfo *pp;
+ int i;
+ /* get security library */
+ if (!EnumerateSecurityPackages (&np,&pp)) {
+ /* look for an SSL package */
+ for (i = 0; (i < (int) np); i++) if (!strcmp (pp[i].Name,UNISP_NAME)) {
+ /* note maximum token size and name */
+ ssltsz = pp[i].cbMaxToken;
+ /* apply runtime linkage */
+ mail_parameters (NIL,SET_SSLDRIVER,(void *) &ssldriver);
+ mail_parameters (NIL,SET_SSLSTART,(void *) ssl_start);
+ return; /* all done */
+ }
+ }
+ }
+}
+
+/* SSL open
+ * Accepts: host name
+ * contact service name
+ * contact port number
+ * Returns: SSL stream if success else NIL
+ */
+
+SSLSTREAM *ssl_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = tcp_open (host,service,port);
+ return stream ? ssl_start (stream,host,port) : NIL;
+}
+
+
+/* SSL authenticated open
+ * Accepts: host name
+ * service name
+ * returned user name buffer
+ * Returns: SSL stream if success else NIL
+ */
+
+SSLSTREAM *ssl_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL; /* don't use this mechanism with SSL */
+}
+
+/* Start SSL/TLS negotiations
+ * Accepts: open TCP stream of session
+ * user's host name
+ * flags
+ * Returns: SSL stream if success else NIL
+ */
+
+static SSLSTREAM *ssl_start (TCPSTREAM *tstream,char *host,unsigned long flags)
+{
+ SECURITY_STATUS e;
+ ULONG a;
+ TimeStamp t;
+ SecBuffer ibuf[2],obuf[1];
+ SecBufferDesc ibufs,obufs;
+ SCHANNEL_CRED tlscred;
+ CERT_CONTEXT *cert = NIL;
+ CERT_CHAIN_PARA chparam;
+ CERT_CHAIN_CONTEXT *chain;
+ SSL_EXTRA_CERT_CHAIN_POLICY_PARA policy;
+ CERT_CHAIN_POLICY_PARA polparam;
+ CERT_CHAIN_POLICY_STATUS status;
+ char tmp[MAILTMPLEN],certname[256];
+ char *reason = NIL;
+ ULONG req = ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT |
+ ISC_REQ_CONFIDENTIALITY | ISC_REQ_USE_SESSION_KEY |
+ ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM | ISC_REQ_EXTENDED_ERROR |
+ ISC_REQ_MANUAL_CRED_VALIDATION;
+ LPSTR usage[] = {
+ szOID_PKIX_KP_SERVER_AUTH,
+ szOID_SERVER_GATED_CRYPTO,
+ szOID_SGC_NETSCAPE
+ };
+ PWSTR whost = NIL;
+ char *buf = (char *) fs_get (ssltsz);
+ unsigned long size = 0;
+ sslcertificatequery_t scq =
+ (sslcertificatequery_t) mail_parameters (NIL,GET_SSLCERTIFICATEQUERY,NIL);
+ sslfailure_t sf = (sslfailure_t) mail_parameters (NIL,GET_SSLFAILURE,NIL);
+ SSLSTREAM *stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
+ sizeof (SSLSTREAM));
+ stream->tcpstream = tstream; /* bind TCP stream */
+ /* initialize TLS credential */
+ memset (&tlscred,0,sizeof (SCHANNEL_CRED));
+ tlscred.dwVersion = SCHANNEL_CRED_VERSION;
+ tlscred.grbitEnabledProtocols = SP_PROT_TLS1;
+
+ /* acquire credentials */
+ if (AcquireCredentialsHandle
+ (NIL,UNISP_NAME,SECPKG_CRED_OUTBOUND,NIL,(flags & NET_TLSCLIENT) ?
+ &tlscred : NIL,NIL,NIL,&stream->cred,&t)
+ != SEC_E_OK) reason = "Acquire credentials handle failed";
+ else while (!reason) { /* negotiate security context */
+ /* initialize buffers */
+ ibuf[0].cbBuffer = size; ibuf[0].pvBuffer = buf;
+ ibuf[1].cbBuffer = 0; ibuf[1].pvBuffer = NIL;
+ obuf[0].cbBuffer = 0; obuf[0].pvBuffer = NIL;
+ ibuf[0].BufferType = obuf[0].BufferType = SECBUFFER_TOKEN;
+ ibuf[1].BufferType = SECBUFFER_EMPTY;
+ /* initialize buffer descriptors */
+ ibufs.ulVersion = obufs.ulVersion = SECBUFFER_VERSION;
+ ibufs.cBuffers = 2; obufs.cBuffers = 1;
+ ibufs.pBuffers = ibuf; obufs.pBuffers = obuf;
+ /* negotiate security */
+ e = InitializeSecurityContext
+ (&stream->cred,size ? &stream->context : NIL,host,req,0,
+ SECURITY_NETWORK_DREP,size? &ibufs:NIL,0,&stream->context,&obufs,&a,&t);
+ /* have an output buffer we need to send? */
+ if (obuf[0].pvBuffer && obuf[0].cbBuffer) {
+ if (!tcp_sout (stream->tcpstream,obuf[0].pvBuffer,obuf[0].cbBuffer))
+ reason = "Unexpected TCP output disconnect";
+ /* free the buffer */
+ FreeContextBuffer (obuf[0].pvBuffer);
+ }
+ if (!reason) switch (e) { /* negotiation state */
+ case SEC_I_INCOMPLETE_CREDENTIALS:
+ break; /* server wants client auth */
+ case SEC_I_CONTINUE_NEEDED:
+ if (size) { /* continue, read any data? */
+ /* yes, anything regurgiated back to us? */
+ if (ibuf[1].BufferType == SECBUFFER_EXTRA) {
+ /* yes, set this as the new data */
+ memmove (buf,buf + size - ibuf[1].cbBuffer,ibuf[1].cbBuffer);
+ size = ibuf[1].cbBuffer;
+ break;
+ }
+ size = 0; /* otherwise, read more stuff from server */
+ }
+ case SEC_E_INCOMPLETE_MESSAGE:
+ /* need to read more data from server */
+ if (!tcp_getdata (stream->tcpstream))
+ reason = "Unexpected TCP input disconnect";
+ else {
+ memcpy (buf+size,stream->tcpstream->iptr,stream->tcpstream->ictr);
+ size += stream->tcpstream->ictr;
+ /* empty it from TCP's buffers */
+ stream->tcpstream->iptr += stream->tcpstream->ictr;
+ stream->tcpstream->ictr = 0;
+ }
+ break;
+
+ case SEC_E_OK: /* success, any data to be regurgitated? */
+ if (ibuf[1].BufferType == SECBUFFER_EXTRA) {
+ /* yes, set this as the new data */
+ memmove (stream->tcpstream->iptr = stream->tcpstream->ibuf,
+ buf + size - ibuf[1].cbBuffer,ibuf[1].cbBuffer);
+ stream->tcpstream->ictr = ibuf[1].cbBuffer;
+ }
+ if (!(flags & NET_NOVALIDATECERT)) {
+ /* need validation, make wchar of host */
+ if (!((size = MultiByteToWideChar (CP_ACP,0,host,-1,NIL,0)) &&
+ (whost = (PWSTR) fs_get (size*sizeof (WCHAR))) &&
+ MultiByteToWideChar (CP_ACP,0,host,-1,whost,size)))
+ fatal ("Can't make wchar of host name!");
+ /* get certificate */
+ if ((QueryContextAttributes
+ (&stream->context,SECPKG_ATTR_REMOTE_CERT_CONTEXT,&cert) !=
+ SEC_E_OK) || !cert) {
+ reason = "*Unable to get certificate";
+ strcpy (certname,"<no certificate>");
+ }
+ else { /* get certificate subject name */
+ CertNameToStr (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ &cert->pCertInfo->Subject,CERT_X500_NAME_STR,
+ certname,255);
+ /* build certificate chain */
+ memset (&chparam,0,sizeof (chparam));
+ chparam.cbSize = sizeof (chparam);
+ chparam.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
+ chparam.RequestedUsage.Usage.rgpszUsageIdentifier = usage;
+ chparam.RequestedUsage.Usage.cUsageIdentifier =
+ sizeof (usage) / sizeof (LPSTR);
+ if (!CertGetCertificateChain
+ (NIL,cert,NIL,cert->hCertStore,&chparam,NIL,NIL,&chain))
+ reason = ssl_analyze_status (GetLastError (),tmp);
+ else { /* validate certificate chain */
+ memset (&policy,0,sizeof (SSL_EXTRA_CERT_CHAIN_POLICY_PARA));
+ policy.cbStruct = sizeof (SSL_EXTRA_CERT_CHAIN_POLICY_PARA);
+ policy.dwAuthType = AUTHTYPE_SERVER;
+ policy.fdwChecks = NIL;
+ policy.pwszServerName = whost;
+ memset (&polparam,0,sizeof (polparam));
+ polparam.cbSize = sizeof (polparam);
+ polparam.pvExtraPolicyPara = &policy;
+ memset (&status,0,sizeof (status));
+ status.cbSize = sizeof (status);
+ if (!CertVerifyCertificateChainPolicy
+ (CERT_CHAIN_POLICY_SSL,chain,&polparam,&status))
+ reason = ssl_analyze_status (GetLastError (),tmp);
+ else if (status.dwError)
+ reason = ssl_analyze_status (status.dwError,tmp);
+ CertFreeCertificateChain (chain);
+ }
+ CertFreeCertificateContext (cert);
+ }
+ if (whost) fs_give ((void **) &whost);
+
+ if (reason) { /* got an error? */
+ /* application callback */
+ if (scq) reason = (*scq) ((*reason == '*') ? reason + 1 : reason,
+ host,certname) ? NIL : "";
+ else if (*certname) { /* error message to return via mm_log() */
+ sprintf (buf,"*%.128s: %.255s",
+ (*reason == '*') ? reason + 1 : reason,certname);
+ reason = buf;
+ }
+ }
+ }
+ if (reason ||
+ (reason = ssl_analyze_status
+ (QueryContextAttributes
+ (&stream->context,SECPKG_ATTR_STREAM_SIZES,&stream->sizes),buf)))
+ break; /* error in certificate or getting sizes */
+ fs_give ((void **) &buf); /* flush temporary buffer */
+ /* make maximum-sized buffers */
+ stream->bufsize = stream->sizes.cbHeader +
+ stream->sizes.cbMaximumMessage + stream->sizes.cbTrailer;
+ if (stream->sizes.cbMaximumMessage < SSLBUFLEN)
+ fatal ("cbMaximumMessage is less than SSLBUFLEN!");
+ else if (stream->sizes.cbMaximumMessage < 16384) {
+ sprintf (tmp,"WINDOWS BUG: cbMaximumMessage = %ld, should be 16384",
+ (long) stream->sizes.cbMaximumMessage);
+ mm_log (tmp,NIL);
+ }
+ stream->ibuf = (char *) fs_get (stream->bufsize);
+ stream->obuf = (char *) fs_get (stream->bufsize);
+ return stream;
+ default:
+ reason = ssl_analyze_status (e,buf);
+ }
+ }
+ ssl_close (stream); /* failed to do SSL */
+ stream = NIL; /* no stream returned */
+ switch (*reason) { /* analyze reason */
+ case '*': /* certificate failure */
+ ++reason; /* skip over certificate failure indication */
+ /* pass to error callback */
+ if (sf) (*sf) (host,reason,flags);
+ else { /* no error callback, build error message */
+ sprintf (tmp,"Certificate failure for %.80s: %.512s",host,reason);
+ mm_log (tmp,ERROR);
+ }
+ case '\0': /* user answered no to certificate callback */
+ if (flags & NET_TRYSSL) /* return dummy stream to stop tryssl */
+ stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
+ sizeof (SSLSTREAM));
+ break;
+ default: /* non-certificate failure */
+ if (flags & NET_TRYSSL); /* no error output if tryssl */
+ /* pass to error callback */
+ else if (sf) (*sf) (host,reason,flags);
+ else { /* no error callback, build error message */
+ sprintf (tmp,"TLS/SSL failure for %.80s: %.512s",host,reason);
+ mm_log (tmp,ERROR);
+ }
+ break;
+ }
+ fs_give ((void **) &buf); /* flush temporary buffer */
+ return stream;
+}
+
+/* Generate error text from SSL error code
+ * Accepts: SSL status
+ * scratch buffer
+ * Returns: text if error status, else NIL
+ */
+
+static char *ssl_analyze_status (SECURITY_STATUS err,char *buf)
+{
+ switch (err) {
+ case SEC_E_OK: /* no error */
+ case SEC_I_CONTINUE_NEEDED:
+ case SEC_I_INCOMPLETE_CREDENTIALS:
+ case SEC_E_INCOMPLETE_MESSAGE:
+ return NIL;
+ case SEC_E_NO_AUTHENTICATING_AUTHORITY:
+ mm_log ("unexpected SEC_E_NO_AUTHENTICATING_AUTHORITY",NIL);
+ return "*No authority could be contacted for authentication";
+ case SEC_E_WRONG_PRINCIPAL:
+ mm_log ("unexpected SEC_E_WRONG_PRINCIPAL",NIL);
+ case CERT_E_CN_NO_MATCH:
+ return "*Server name does not match certificate";
+ case SEC_E_UNTRUSTED_ROOT:
+ mm_log ("unexpected SEC_E_UNTRUSTED_ROOT",NIL);
+ case CERT_E_UNTRUSTEDROOT:
+ return "*Self-signed certificate or untrusted authority";
+ case SEC_E_CERT_EXPIRED:
+ mm_log ("unexpected SEC_E_CERT_EXPIRED",NIL);
+ case CERT_E_EXPIRED:
+ return "*Certificate has expired";
+ case CERT_E_REVOKED:
+ return "*Certificate revoked";
+ case SEC_E_INVALID_TOKEN:
+ return "Invalid token, probably not an SSL server";
+ case SEC_E_UNSUPPORTED_FUNCTION:
+ return "SSL not supported on this machine - upgrade your system software";
+ }
+ sprintf (buf,"Unexpected SSPI or certificate error %lx - report this",err);
+ return buf;
+}
+
+/* SSL receive line
+ * Accepts: SSL stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *ssl_getline (SSLSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = ssl_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = ssl_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* SSL receive line or partial line
+ * Accepts: SSL stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!ssl_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!ssl_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* SSL receive buffer
+ * Accepts: SSL stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long ssl_getbuffer (SSLSTREAM *stream,unsigned long size,char *buffer)
+{
+ unsigned long n;
+ while (size > 0) { /* until request satisfied */
+ if (!ssl_getdata (stream)) return NIL;
+ n = min (size,stream->ictr);/* number of bytes to transfer */
+ /* do the copy */
+ memcpy (buffer,stream->iptr,n);
+ buffer += n; /* update pointer */
+ stream->iptr += n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -= n;
+ }
+ buffer[0] = '\0'; /* tie off string */
+ return T;
+}
+
+/* SSL receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long ssl_getdata (SSLSTREAM *stream)
+{
+ while (stream->ictr < 1) { /* decrypted buffer empty? */
+ SECURITY_STATUS status;
+ SecBuffer buf[4];
+ SecBufferDesc msg;
+ size_t i;
+ size_t n = 0; /* initially no bytes to decrypt */
+ do { /* yes, make sure have data from TCP */
+ if (stream->iextractr) { /* have previous unread data? */
+ memcpy (stream->ibuf + n,stream->iextraptr,stream->iextractr);
+ n += stream->iextractr; /* update number of bytes read */
+ stream->iextractr = 0; /* no more extra data */
+ }
+ else { /* read from TCP */
+ if (!tcp_getdata (stream->tcpstream)) return ssl_abort (stream);
+ /* maximum amount of data to copy */
+ if (!(i = min (stream->bufsize - n,stream->tcpstream->ictr)))
+ fatal ("incomplete SecBuffer exceeds maximum buffer size");
+ /* do the copy */
+ memcpy (stream->ibuf + n,stream->tcpstream->iptr,i);
+ stream->tcpstream->iptr += i;
+ stream->tcpstream->ictr -= i;
+ n += i; /* update number of bytes to decrypt */
+ }
+ buf[0].cbBuffer = n; /* first SecBuffer gets data */
+ buf[0].pvBuffer = stream->ibuf;
+ buf[0].BufferType = SECBUFFER_DATA;
+ /* subsequent ones are for spares */
+ buf[1].BufferType = buf[2].BufferType = buf[3].BufferType =
+ SECBUFFER_EMPTY;
+ msg.ulVersion = SECBUFFER_VERSION;
+ msg.cBuffers = 4; /* number of SecBuffers */
+ msg.pBuffers = buf; /* first SecBuffer */
+ } while ((status = DecryptMessage
+ (&stream->context,&msg,0,NIL)) == SEC_E_INCOMPLETE_MESSAGE);
+ switch (status) {
+ case SEC_E_OK: /* won */
+ case SEC_I_RENEGOTIATE: /* won but lost it after this buffer */
+ /* hunt for a buffer */
+ for (i = 0; (i < 4) && (buf[i].BufferType != SECBUFFER_DATA) ; i++);
+ if (i < 4) { /* found a buffer? */
+ /* yes, set up pointer and counter */
+ stream->iptr = buf[i].pvBuffer;
+ stream->ictr = buf[i].cbBuffer;
+ /* any unprocessed data? */
+ while (++i < 4) if (buf[i].BufferType == SECBUFFER_EXTRA) {
+ /* yes, note for next time around */
+ stream->iextraptr = buf[i].pvBuffer;
+ stream->iextractr = buf[i].cbBuffer;
+ }
+ }
+ break;
+ default: /* anything else means we've lost */
+ return ssl_abort (stream);
+ }
+ }
+ return LONGT;
+}
+
+/* SSL send string as record
+ * Accepts: SSL stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long ssl_soutr (SSLSTREAM *stream,char *string)
+{
+ return ssl_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* SSL send string
+ * Accepts: SSL stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long ssl_sout (SSLSTREAM *stream,char *string,unsigned long size)
+{
+ SecBuffer buf[4];
+ SecBufferDesc msg;
+ char *s;
+ size_t n;
+ if (!stream->tcpstream) return NIL;
+ /* until request satisfied */
+ for (s = stream->ibuf,n = 0; size;) {
+ /* header */
+ buf[0].BufferType = SECBUFFER_STREAM_HEADER;
+ memset (buf[0].pvBuffer = stream->obuf,0,
+ buf[0].cbBuffer = stream->sizes.cbHeader);
+ /* message (up to maximum size) */
+ buf[1].BufferType = SECBUFFER_DATA;
+ memcpy (buf[1].pvBuffer = stream->obuf + stream->sizes.cbHeader,string,
+ buf[1].cbBuffer = min (size,SSLBUFLEN));
+ /* trailer */
+ buf[2].BufferType = SECBUFFER_STREAM_TRAILER;
+ memset (buf[2].pvBuffer = ((char *) buf[1].pvBuffer) + buf[1].cbBuffer,0,
+ buf[2].cbBuffer = stream->sizes.cbTrailer);
+ /* spare */
+ buf[3].BufferType = SECBUFFER_EMPTY;
+ msg.ulVersion = SECBUFFER_VERSION;
+ msg.cBuffers = 4; /* number of SecBuffers */
+ msg.pBuffers = buf; /* first SecBuffer */
+ string += buf[1].cbBuffer;
+ size -= buf[1].cbBuffer; /* this many bytes processed */
+ /* encrypt and send message */
+ if ((EncryptMessage
+ (&stream->context,0,&msg,NIL) != SEC_E_OK) ||
+ !tcp_sout (stream->tcpstream,stream->obuf,
+ buf[0].cbBuffer + buf[1].cbBuffer + buf[2].cbBuffer))
+ return ssl_abort (stream);/* encryption or sending failed */
+ }
+ return LONGT;
+}
+
+/* SSL close
+ * Accepts: SSL stream
+ */
+
+void ssl_close (SSLSTREAM *stream)
+{
+ ssl_abort (stream); /* nuke the stream */
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* SSL abort stream
+ * Accepts: SSL stream
+ * Returns: NIL always
+ */
+
+static long ssl_abort (SSLSTREAM *stream)
+{
+ if (stream->tcpstream) { /* close TCP stream */
+ DeleteSecurityContext (&stream->context);
+ FreeCredentialHandle (&stream->cred);
+ tcp_close (stream->tcpstream);
+ stream->tcpstream = NIL;
+ }
+ if (stream->ibuf) fs_give ((void **) &stream->ibuf);
+ if (stream->obuf) fs_give ((void **) &stream->obuf);
+ return NIL;
+}
+
+/* SSL get host name
+ * Accepts: SSL stream
+ * Returns: host name for this stream
+ */
+
+char *ssl_host (SSLSTREAM *stream)
+{
+ return tcp_host (stream->tcpstream);
+}
+
+
+/* SSL get remote host name
+ * Accepts: SSL stream
+ * Returns: host name for this stream
+ */
+
+char *ssl_remotehost (SSLSTREAM *stream)
+{
+ return tcp_remotehost (stream->tcpstream);
+}
+
+
+/* SSL return port for this stream
+ * Accepts: SSL stream
+ * Returns: port number for this stream
+ */
+
+unsigned long ssl_port (SSLSTREAM *stream)
+{
+ return tcp_port (stream->tcpstream);
+}
+
+
+/* SSL get local host name
+ * Accepts: SSL stream
+ * Returns: local host name
+ */
+
+char *ssl_localhost (SSLSTREAM *stream)
+{
+ return tcp_localhost (stream->tcpstream);
+}
+
+#include "ssl_none.c" /* currently no server support */
diff --git a/imap/src/osdep/nt/tcp_nt.c b/imap/src/osdep/nt/tcp_nt.c
new file mode 100644
index 00000000..a6d735dc
--- /dev/null
+++ b/imap/src/osdep/nt/tcp_nt.c
@@ -0,0 +1,916 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Winsock TCP/IP routines
+ *
+ * Author: Mark Crispin from Mike Seibel's Winsock code
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 13 January 2007
+ */
+
+#include "ip_nt.c"
+
+
+#define TCPMAXSEND 32768
+
+/* Private functions */
+
+int tcp_socket_open (int family,void *adr,size_t adrlen,unsigned short port,
+ char *tmp,char *hst);
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd);
+long tcp_abort (TCPSTREAM *stream);
+long tcp_close_socket (SOCKET *sock);
+char *tcp_name (struct sockaddr *sadr,long flag);
+char *tcp_name_valid (char *s);
+
+
+/* Private data */
+
+int wsa_initted = 0; /* init ? */
+static int wsa_sock_open = 0; /* keep track of open sockets */
+static tcptimeout_t tmoh = NIL; /* TCP timeout handler routine */
+static long ttmo_open = 0; /* TCP timeouts, in seconds */
+static long ttmo_read = 0;
+static long ttmo_write = 0;
+static long allowreversedns = T;/* allow reverse DNS lookup */
+static long tcpdebug = NIL; /* extra TCP debugging telemetry */
+static char *myClientAddr = NIL;/* client host address */
+static char *myClientHost = NIL;/* client host name */
+static long myClientPort = -1; /* client port */
+static char *myServerAddr = NIL;/* server host address */
+static char *myServerHost = NIL;/* server host name */
+static long myServerPort = -1; /* server port */
+
+extern long maxposint; /* get this from write.c */
+
+/* TCP/IP manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tcp_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_TIMEOUT:
+ tmoh = (tcptimeout_t) value;
+ case GET_TIMEOUT:
+ ret = (void *) tmoh;
+ break;
+ case SET_OPENTIMEOUT:
+ ttmo_open = (long) value ? (long) value : (long) WSA_INFINITE;
+ case GET_OPENTIMEOUT:
+ ret = (void *) ttmo_open;
+ break;
+ case SET_READTIMEOUT:
+ ttmo_read = (long) value;
+ case GET_READTIMEOUT:
+ ret = (void *) ttmo_read;
+ break;
+ case SET_WRITETIMEOUT:
+ ttmo_write = (long) value;
+ case GET_WRITETIMEOUT:
+ ret = (void *) ttmo_write;
+ break;
+ case SET_ALLOWREVERSEDNS:
+ allowreversedns = (long) value;
+ case GET_ALLOWREVERSEDNS:
+ ret = (void *) allowreversedns;
+ break;
+ case SET_TCPDEBUG:
+ tcpdebug = (long) value;
+ case GET_TCPDEBUG:
+ ret = (void *) tcpdebug;
+ break;
+ }
+ return ret;
+}
+
+/* TCP/IP open
+ * Accepts: host name
+ * contact service name
+ * contact port number and optional silent flag
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = NIL;
+ int i,family;
+ SOCKET sock = INVALID_SOCKET;
+ int silent = (port & NET_SILENT) ? T : NIL;
+ char *s,*hostname,tmp[MAILTMPLEN];
+ void *adr,*next;
+ size_t adrlen;
+ struct servent *sv = NIL;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (!wsa_initted++) { /* init Windows Sockets */
+ WSADATA wsock;
+ if (i = (int) WSAStartup (WINSOCK_VERSION,&wsock)) {
+ wsa_initted = 0; /* in case we try again */
+ sprintf (tmp,"Unable to start Windows Sockets (%d)",i);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ }
+ port &= 0xffff; /* erase flags */
+ /* lookup service */
+ if (service && (sv = getservbyname (service,"tcp")))
+ port = ntohs (sv->s_port);
+ /* The domain literal form is used (rather than simply the dotted decimal
+ as with other Windows programs) because it has to be a valid "host name"
+ in mailsystem terminology. */
+ /* look like domain literal? */
+ if (host[0] == '[' && host[(strlen (host))-1] == ']') {
+ strcpy (tmp,host+1); /* yes, copy number part */
+ tmp[strlen (tmp)-1] = '\0';
+ if (adr = ip_stringtoaddr (tmp,&adrlen,&family)) {
+ (*bn) (BLOCK_TCPOPEN,NIL);
+ sock = tcp_socket_open (family,adr,adrlen,(unsigned short) port,tmp,
+ hostname = host);
+ (*bn) (BLOCK_NONE,NIL);
+ fs_give ((void **) &adr);
+ }
+ else sprintf (tmp,"Bad format domain-literal: %.80s",host);
+ }
+
+ else { /* lookup host name */
+ if (tcpdebug) {
+ sprintf (tmp,"DNS resolution %.80s",host);
+ mm_log (tmp,TCPDEBUG);
+ }
+ (*bn) (BLOCK_DNSLOOKUP,NIL);/* look up name */
+ if (!(s = ip_nametoaddr (host,&adrlen,&family,&hostname,&next)))
+ sprintf (tmp,"Host not found (#%d): %s",WSAGetLastError (),host);
+ (*bn) (BLOCK_NONE,NIL);
+ if (s) { /* DNS resolution won? */
+ if (tcpdebug) mm_log ("DNS resolution done",TCPDEBUG);
+ wsa_sock_open++; /* prevent tcp_close_socket() from freeing in
+ loop */
+ do {
+ (*bn) (BLOCK_TCPOPEN,NIL);
+ if (((sock = tcp_socket_open (family,s,adrlen,(unsigned short) port,
+ tmp,hostname)) == INVALID_SOCKET) &&
+ (s = ip_nametoaddr (NIL,&adrlen,&family,&hostname,&next)) &&
+ !silent) mm_log (tmp,WARN);
+ (*bn) (BLOCK_NONE,NIL);
+ } while ((sock == INVALID_SOCKET) && s);
+ wsa_sock_open--; /* undo protection */
+ }
+ }
+ if (sock == INVALID_SOCKET) { /* do possible cleanup action */
+ if (!silent) mm_log (tmp,ERROR);
+ tcp_close_socket (&sock);
+ }
+ else { /* got a socket, create TCP/IP stream */
+ stream = (TCPSTREAM *) memset (fs_get (sizeof (TCPSTREAM)),0,
+ sizeof (TCPSTREAM));
+ stream->port = port; /* port number */
+ /* init socket */
+ stream->tcpsi = stream->tcpso = sock;
+ stream->ictr = 0; /* init input counter */
+ /* copy official host name */
+ stream->host = cpystr (hostname);
+ if (tcpdebug) mm_log ("Stream open and ready for read",TCPDEBUG);
+ }
+ return stream; /* return success */
+}
+
+/* Open a TCP socket
+ * Accepts: protocol family
+ * address to connect to
+ * address length
+ * port
+ * scratch buffer
+ * host name
+ * Returns: socket if success, else SOCKET_ERROR with error string in scratch
+ */
+
+int tcp_socket_open (int family,void *adr,size_t adrlen,unsigned short port,
+ char *tmp,char *hst)
+{
+ int sock,err;
+ char *s,errmsg[100];
+ size_t len;
+ DWORD eo;
+ WSAEVENT event;
+ WSANETWORKEVENTS events;
+ unsigned long cmd = 0;
+ struct protoent *pt = getprotobyname ("tcp");
+ struct sockaddr *sadr = ip_sockaddr (family,adr,adrlen,port,&len);
+ sprintf (tmp,"Trying IP address [%s]",ip_sockaddrtostring (sadr));
+ mm_log (tmp,NIL);
+ /* get a TCP stream */
+ if ((sock = socket (sadr->sa_family,SOCK_STREAM,pt ? pt->p_proto : 0)) ==
+ INVALID_SOCKET)
+ sprintf (tmp,"Unable to create TCP socket (%d)",WSAGetLastError ());
+ else {
+ /* On Windows, FD_SETSIZE is the number of descriptors which can be
+ * held in an fd_set, as opposed to the maximum descriptor value on UNIX.
+ * Similarly, an fd_set in Windows is a vector of descriptor values, as
+ * opposed to a bitmask of set fds on UNIX. Thus, an fd_set can hold up
+ * to FD_SETSIZE values which can be larger than FD_SETSIZE, and the test
+ * that is used on UNIX is unnecessary here.
+ */
+ wsa_sock_open++; /* count this socket as open */
+ /* set socket nonblocking */
+ if (ttmo_open) WSAEventSelect (sock,event = WSACreateEvent (),FD_CONNECT);
+ else event = 0; /* no event */
+ /* open connection */
+ err = (connect (sock,sadr,len) == SOCKET_ERROR) ? WSAGetLastError () : NIL;
+ /* if timer in effect, wait for event */
+ if (event) while (err == WSAEWOULDBLOCK)
+ switch (eo = WSAWaitForMultipleEvents (1,&event,T,ttmo_open*1000,NIL)) {
+ case WSA_WAIT_EVENT_0: /* got an event? */
+ err = (WSAEnumNetworkEvents (sock,event,&events) == SOCKET_ERROR) ?
+ WSAGetLastError () : events.iErrorCode[FD_CONNECT_BIT];
+ break;
+ case WSA_WAIT_IO_COMPLETION:
+ break; /* fAlertable is NIL so shouldn't happen */
+ default: /* all other conditions */
+ err = eo; /* error from WSAWaitForMultipleEvents() */
+ break;
+ }
+
+ switch (err) { /* analyze result from connect and wait */
+ case 0: /* got a connection */
+ s = NIL;
+ if (event) { /* unset blocking mode */
+ WSAEventSelect (sock,event,NIL);
+ if (ioctlsocket (sock,FIONBIO,&cmd) == SOCKET_ERROR)
+ sprintf (s = errmsg,"Can't set blocking mode (%d)",
+ WSAGetLastError ());
+ }
+ break;
+ case WSAECONNREFUSED:
+ s = "Refused";
+ break;
+ case WSAENOBUFS:
+ s = "Insufficient system resources";
+ break;
+ case WSA_WAIT_TIMEOUT: case WSAETIMEDOUT:
+ s = "Timed out";
+ break;
+ case WSAEHOSTUNREACH:
+ s = "Host unreachable";
+ break;
+ default: /* horrible error 69 */
+ sprintf (s = errmsg,"Unknown error (%d)",err);
+ break;
+ }
+ /* flush event */
+ if (event) WSACloseEvent (event);
+ if (s) { /* got an error? */
+ sprintf (tmp,"Can't connect to %.80s,%ld: %.80s",hst,port,s);
+ tcp_close_socket (&sock); /* flush socket */
+ sock = INVALID_SOCKET;
+ }
+ }
+ fs_give ((void **) &sadr); /* and socket address */
+ return sock; /* return the socket */
+}
+
+/* TCP/IP authenticated open
+ * Accepts: NETMBX specifier
+ * service name
+ * returned user name buffer
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL; /* always NIL on Windows */
+}
+
+/* TCP receive line
+ * Accepts: TCP stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *tcp_getline (TCPSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = tcp_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = tcp_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* TCP receive line or partial line
+ * Accepts: TCP stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!tcp_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!tcp_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* TCP/IP receive buffer
+ * Accepts: TCP/IP stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *s)
+{
+ unsigned long n;
+ /* make sure socket still alive */
+ if (stream->tcpsi == INVALID_SOCKET) return NIL;
+ /* can transfer bytes from buffer? */
+ if (n = min (size,stream->ictr)) {
+ memcpy (s,stream->iptr,n); /* yes, slurp as much as we can from it */
+ s += n; /* update pointer */
+ stream->iptr +=n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -=n;
+ }
+ if (size) {
+ int i;
+ fd_set fds,efds;
+ struct timeval tmo;
+ time_t t = time (0);
+ blocknotify_t bn=(blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ (*bn) (BLOCK_TCPREAD,NIL);
+ while (size > 0) { /* until request satisfied */
+ if (tcpdebug) mm_log ("Reading TCP buffer",TCPDEBUG);
+ /* simple case if not a socket */
+ if (stream->tcpsi != stream->tcpso)
+ while (((i = read (stream->tcpsi,s,(int) min (maxposint,size))) < 0) &&
+ (errno == EINTR));
+ else { /* socket case */
+ time_t tl = time (0);
+ time_t now = tl;
+ time_t ti = ttmo_read ? now + ttmo_read : 0;
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ /* set bit in selection vectors */
+ FD_SET (stream->tcpsi,&fds);
+ FD_SET (stream->tcpsi,&efds);
+ errno = NIL; /* initially no error */
+ do { /* block under timeout */
+ tmo.tv_sec = (long) (ti ? ti - now : 0);
+ i = select (stream->tcpsi+1,&fds,NIL,&efds,ti ? &tmo : NIL);
+ now = time (0); /* fake timeout if interrupt & time expired */
+ if ((i < 0) && ((errno = WSAGetLastError ()) == WSAEINTR) && ti &&
+ (ti <= now)) i = 0;
+ } while ((i < 0) && (errno == WSAEINTR));
+ /* success from select, read what we can */
+ if (i > 0) while (((i = recv (stream->tcpsi,s,
+ (int) min (maxposint,size),0)) ==
+ SOCKET_ERROR) &&
+ ((errno = WSAGetLastError ()) == WSAEINTR));
+ else if (!i) { /* timeout, ignore if told to resume */
+ if (tmoh && (*tmoh) ((long) (now - t),(long) (now - tl),
+ stream->host))
+ continue;
+ /* otherwise punt */
+ if (tcpdebug) mm_log ("TCP buffer read timeout",TCPDEBUG);
+ return tcp_abort (stream);
+ }
+ }
+ if (i <= 0) { /* error seen? */
+ if (tcpdebug) {
+ char tmp[MAILTMPLEN];
+ if (i) sprintf (s = tmp,"TCP buffer read I/O error %d",errno);
+ else s = "TCP buffer read end of file";
+ mm_log (s,TCPDEBUG);
+ }
+ return tcp_abort (stream);
+ }
+ s += i; /* point at new place to write */
+ size -= i; /* reduce byte count */
+ if (tcpdebug) mm_log ("Successfully read TCP buffer",TCPDEBUG);
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ }
+ *s = '\0'; /* tie off string */
+ return LONGT;
+}
+
+/* TCP/IP receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getdata (TCPSTREAM *stream)
+{
+ int i;
+ fd_set fds,efds;
+ struct timeval tmo;
+ time_t t = time (0);
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (stream->tcpsi == INVALID_SOCKET) return NIL;
+ (*bn) (BLOCK_TCPREAD,NIL);
+ while (stream->ictr < 1) { /* if nothing in the buffer */
+ if (tcpdebug) mm_log ("Reading TCP data",TCPDEBUG);
+ /* simple case if not a socket */
+ if (stream->tcpsi != stream->tcpso)
+ while (((i = read (stream->tcpsi,stream->ibuf,BUFLEN)) < 0) &&
+ (errno == EINTR));
+ else {
+ time_t tl = time (0);
+ time_t now = tl;
+ time_t ti = ttmo_read ? now + ttmo_read : 0;
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ /* set bit in selection vectors */
+ FD_SET (stream->tcpsi,&fds);
+ FD_SET (stream->tcpsi,&efds);
+ errno = NIL; /* initially no error */
+ do { /* block under timeout */
+ tmo.tv_sec = (long) (ti ? ti - now : 0);
+ i = select (stream->tcpsi+1,&fds,NIL,&efds,ti ? &tmo : NIL);
+ now = time (0); /* fake timeout if interrupt & time expired */
+ if ((i < 0) && ((errno = WSAGetLastError ()) == WSAEINTR) && ti &&
+ (ti <= now)) i = 0;
+ } while ((i < 0) && (errno == WSAEINTR));
+ /* success from select, read what we can */
+ if (i > 0) while (((i = recv (stream->tcpsi,stream->ibuf,BUFLEN,0)) ==
+ SOCKET_ERROR) &&
+ ((errno = WSAGetLastError ()) == WSAEINTR));
+ else if (!i) { /* timeout, ignore if told to resume */
+ if (tmoh && (*tmoh) ((long) (now - t),(long) (now - tl), stream->host))
+ continue;
+ /* otherwise punt */
+ if (tcpdebug) mm_log ("TCP data read timeout",TCPDEBUG);
+ return tcp_abort (stream);
+ }
+ }
+ if (i <= 0) { /* error seen? */
+ if (tcpdebug) {
+ char *s,tmp[MAILTMPLEN];
+ if (i) sprintf (s = tmp,"TCP data read I/O error %d",errno);
+ else s = "TCP data read end of file";
+ mm_log (tmp,TCPDEBUG);
+ }
+ return tcp_abort (stream);
+ }
+ stream->iptr = stream->ibuf;/* point at TCP buffer */
+ stream->ictr = i; /* set new byte count */
+ if (tcpdebug) mm_log ("Successfully read TCP data",TCPDEBUG);
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return T;
+}
+
+/* TCP/IP send string as record
+ * Accepts: TCP/IP stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long tcp_soutr (TCPSTREAM *stream,char *string)
+{
+ return tcp_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* TCP/IP send string
+ * Accepts: TCP/IP stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
+{
+ int i;
+ struct timeval tmo;
+ fd_set fds,efds;
+ time_t t = time (0);
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ tmo.tv_sec = ttmo_write;
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ if (stream->tcpso == INVALID_SOCKET) return NIL;
+ (*bn) (BLOCK_TCPWRITE,NIL);
+ while (size > 0) { /* until request satisfied */
+ if (tcpdebug) mm_log ("Writing to TCP",TCPDEBUG);
+ /* simple case if not a socket */
+ if (stream->tcpsi != stream->tcpso)
+ while (((i = write (stream->tcpso,string,min (size,TCPMAXSEND))) < 0) &&
+ (errno == EINTR));
+ else {
+ time_t tl = time (0); /* start of request */
+ time_t now = tl;
+ time_t ti = ttmo_write ? now + ttmo_write : 0;
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ /* set bit in selection vectors */
+ FD_SET (stream->tcpso,&fds);
+ FD_SET(stream->tcpso,&efds);
+ errno = NIL; /* block and write */
+ do { /* block under timeout */
+ tmo.tv_sec = (long) (ti ? ti - now : 0);
+ i = select (stream->tcpso+1,NIL,&fds,&efds,ti ? &tmo : NIL);
+ now = time (0); /* fake timeout if interrupt & time expired */
+ if ((i < 0) && ((errno = WSAGetLastError ()) == WSAEINTR) && ti &&
+ (ti <= now)) i = 0;
+ } while ((i < 0) && (errno == WSAEINTR));
+ /* OK to send data? */
+ if (i > 0) while (((i = send (stream->tcpso,string,
+ (int) min (size,TCPMAXSEND),0)) ==
+ SOCKET_ERROR) &&
+ ((errno = WSAGetLastError ()) == WSAEINTR));
+ else if (!i) { /* timeout, ignore if told to resume */
+ if (tmoh && (*tmoh) ((long) (now - t),(long) (now - tl), stream->host))
+ continue;
+ /* otherwise punt */
+ if (tcpdebug) mm_log ("TCP write timeout",TCPDEBUG);
+ return tcp_abort (stream);
+ }
+ }
+ if (i <= 0) { /* error seen? */
+ if (tcpdebug) {
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"TCP write I/O error %d",errno);
+ mm_log (tmp,TCPDEBUG);
+ }
+ return tcp_abort (stream);
+ }
+ string += i; /* how much we sent */
+ size -= i; /* count this size */
+ if (tcpdebug) mm_log ("successfully wrote to TCP",TCPDEBUG);
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return T; /* all done */
+}
+
+/* TCP/IP close
+ * Accepts: TCP/IP stream
+ */
+
+void tcp_close (TCPSTREAM *stream)
+{
+ tcp_abort (stream); /* nuke the sockets */
+ /* flush host names */
+ if (stream->host) fs_give ((void **) &stream->host);
+ if (stream->remotehost) fs_give ((void **) &stream->remotehost);
+ if (stream->localhost) fs_give ((void **) &stream->localhost);
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* TCP/IP abort sockets
+ * Accepts: TCP/IP stream
+ * Returns: NIL, always
+ */
+
+long tcp_abort (TCPSTREAM *stream)
+{
+ if (stream->tcpsi != stream->tcpso) tcp_close_socket (&stream->tcpso);
+ else stream->tcpso = INVALID_SOCKET;
+ return tcp_close_socket (&stream->tcpsi);
+}
+
+
+/* TCP/IP abort stream
+ * Accepts: WinSock socket
+ * Returns: NIL, always
+ */
+
+long tcp_close_socket (SOCKET *sock)
+{
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* something to close? */
+ if (sock && (*sock != INVALID_SOCKET)) {
+ (*bn) (BLOCK_TCPCLOSE,NIL);
+ closesocket (*sock); /* WinSock socket close */
+ *sock = INVALID_SOCKET;
+ (*bn) (BLOCK_NONE,NIL);
+ wsa_sock_open--; /* drop this socket */
+ }
+ /* no more open streams? */
+ if (wsa_initted && !wsa_sock_open) {
+ mm_log ("Winsock cleanup",NIL);
+ wsa_initted = 0; /* no more sockets, so... */
+ WSACleanup (); /* free up resources until needed */
+ }
+ return NIL;
+}
+
+/* TCP/IP get host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_host (TCPSTREAM *stream)
+{
+ return stream->host; /* use tcp_remotehost() if want guarantees */
+}
+
+
+/* TCP/IP get remote host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_remotehost (TCPSTREAM *stream)
+{
+ if (!stream->remotehost) {
+ size_t sadrlen;
+ struct sockaddr *sadr = ip_newsockaddr (&sadrlen);
+ stream->remotehost = /* get socket's peer name */
+ ((getpeername (stream->tcpsi,sadr,&sadrlen) == SOCKET_ERROR) ||
+ (sadrlen <= 0)) ? cpystr (stream->host) : tcp_name (sadr,NIL);
+ fs_give ((void **) &sadr);
+ }
+ return stream->remotehost;
+}
+
+
+/* TCP/IP return port for this stream
+ * Accepts: TCP/IP stream
+ * Returns: port number for this stream
+ */
+
+unsigned long tcp_port (TCPSTREAM *stream)
+{
+ return stream->port; /* return port number */
+}
+
+
+/* TCP/IP get local host name
+ * Accepts: TCP/IP stream
+ * Returns: local host name
+ */
+
+char *tcp_localhost (TCPSTREAM *stream)
+{
+ if (!stream->localhost) {
+ size_t sadrlen;
+ struct sockaddr *sadr = ip_newsockaddr (&sadrlen);
+ stream->localhost = /* get socket's name */
+ ((stream->port & 0xffff000) ||
+ ((getsockname (stream->tcpsi,sadr,&sadrlen) == SOCKET_ERROR) ||
+ (sadrlen <= 0))) ? cpystr (mylocalhost ()) : tcp_name (sadr,NIL);
+ fs_give ((void **) &sadr);
+ }
+ return stream->localhost; /* return local host name */
+}
+
+/* TCP/IP get client host address (server calls only)
+ * Returns: client host address
+ */
+
+char *tcp_clientaddr ()
+{
+ if (!myClientAddr) {
+ size_t sadrlen;
+ struct sockaddr *sadr = ip_newsockaddr (&sadrlen);
+ if ((getpeername (0,sadr,(void *) &sadrlen) == SOCKET_ERROR) ||
+ (sadrlen <= 0)) myClientAddr = cpystr ("UNKNOWN");
+ else { /* get stdin's peer name */
+ myClientAddr = cpystr (ip_sockaddrtostring (sadr));
+ if (myClientPort < 0) myClientPort = ip_sockaddrtoport (sadr);
+ }
+ fs_give ((void **) &sadr);
+ }
+ return myClientAddr;
+}
+
+
+/* TCP/IP get client host name (server calls only)
+ * Returns: client host name
+ */
+
+char *tcp_clienthost ()
+{
+ if (!myClientHost) {
+ size_t sadrlen;
+ struct sockaddr *sadr = ip_newsockaddr (&sadrlen);
+ if ((getpeername (0,sadr,(void *) &sadrlen) == SOCKET_ERROR) ||
+ (sadrlen <= 0)) myClientHost = cpystr ("UNKNOWN");
+ else { /* get stdin's peer name */
+ myClientHost = tcp_name (sadr,T);
+ if (!myClientAddr) myClientAddr = cpystr (ip_sockaddrtostring (sadr));
+ if (myClientPort < 0) myClientPort = ip_sockaddrtoport (sadr);
+ }
+ fs_give ((void **) &sadr);
+ }
+ return myClientHost;
+}
+
+
+/* TCP/IP get client port number (server calls only)
+ * Returns: client port number
+ */
+
+long tcp_clientport ()
+{
+ if (!myClientHost && !myClientAddr) tcp_clientaddr ();
+ return myClientPort;
+}
+
+/* TCP/IP get server host address (server calls only)
+ * Returns: server host address
+ */
+
+char *tcp_serveraddr ()
+{
+ if (!myServerAddr) {
+ size_t sadrlen;
+ struct sockaddr *sadr = ip_newsockaddr (&sadrlen);
+ if (!wsa_initted++) { /* init Windows Sockets */
+ WSADATA wsock;
+ if (WSAStartup (WINSOCK_VERSION,&wsock)) {
+ wsa_initted = 0;
+ return "random-pc"; /* try again later? */
+ }
+ }
+ if ((getsockname (0,sadr,(void *) &sadrlen) == SOCKET_ERROR) ||
+ (sadrlen <= 0)) myServerAddr = cpystr ("UNKNOWN");
+ else { /* get stdin's name */
+ myServerAddr = cpystr (ip_sockaddrtostring (sadr));
+ if (myServerPort < 0) myServerPort = ip_sockaddrtoport (sadr);
+ }
+ fs_give ((void **) &sadr);
+ }
+ return myServerAddr;
+}
+
+
+/* TCP/IP get server host name (server calls only)
+ * Returns: server host name
+ */
+
+char *tcp_serverhost ()
+{
+ if (!myServerHost) { /* once-only */
+ size_t sadrlen;
+ struct sockaddr *sadr = ip_newsockaddr (&sadrlen);
+ if (!wsa_initted++) { /* init Windows Sockets */
+ WSADATA wsock;
+ if (WSAStartup (WINSOCK_VERSION,&wsock)) {
+ wsa_initted = 0;
+ return "random-pc"; /* try again later? */
+ }
+ }
+ /* get stdin's name */
+ if ((getsockname (0,sadr,(void *) &sadrlen) == SOCKET_ERROR) ||
+ (sadrlen <= 0)) myServerHost = cpystr (mylocalhost ());
+ else { /* get stdin's name */
+ myServerHost = tcp_name (sadr,NIL);
+ if (!myServerAddr) myServerAddr = cpystr (ip_sockaddrtostring (sadr));
+ if (myServerPort < 0) myServerPort = ip_sockaddrtoport (sadr);
+ }
+ fs_give ((void **) &sadr);
+ }
+ return myServerHost;
+}
+
+
+/* TCP/IP get server port number (server calls only)
+ * Returns: server port number
+ */
+
+long tcp_serverport ()
+{
+ if (!myServerHost && !myServerAddr) tcp_serveraddr ();
+ return myServerPort;
+}
+
+/* TCP/IP return canonical form of host name
+ * Accepts: host name
+ * Returns: canonical form of host name
+ */
+
+char *tcp_canonical (char *name)
+{
+ char *ret,host[MAILTMPLEN];
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* look like domain literal? */
+ if (name[0] == '[' && name[strlen (name) - 1] == ']') return name;
+ (*bn) (BLOCK_DNSLOOKUP,NIL);
+ if (tcpdebug) {
+ sprintf (host,"DNS canonicalization %.80s",name);
+ mm_log (host,TCPDEBUG);
+ }
+ /* get canonical name */
+ if (!ip_nametoaddr (name,NIL,NIL,&ret,NIL)) ret = name;
+ (*bn) (BLOCK_NONE,NIL); /* alarms OK now */
+ if (tcpdebug) mm_log ("DNS canonicalization done",TCPDEBUG);
+ return ret;
+}
+
+
+/* TCP/IP return name from socket
+ * Accepts: socket
+ * verbose flag
+ * Returns: cpystr name
+ */
+
+char *tcp_name (struct sockaddr *sadr,long flag)
+{
+ char *ret,*t,adr[MAILTMPLEN],tmp[MAILTMPLEN];
+ sprintf (ret = adr,"[%.80s]",ip_sockaddrtostring (sadr));
+ if (allowreversedns) {
+ blocknotify_t bn = (blocknotify_t)mail_parameters(NIL,GET_BLOCKNOTIFY,NIL);
+ if (tcpdebug) {
+ sprintf (tmp,"Reverse DNS resolution %s",adr);
+ mm_log (tmp,TCPDEBUG);
+ }
+ (*bn) (BLOCK_DNSLOOKUP,NIL);/* quell alarms */
+ /* translate address to name */
+ if (t = tcp_name_valid (ip_sockaddrtoname (sadr))) {
+ /* produce verbose form if needed */
+ if (flag) sprintf (ret = tmp,"%s %s",t,adr);
+ else ret = t;
+ }
+ (*bn) (BLOCK_NONE,NIL); /* alarms OK now */
+ if (tcpdebug) mm_log ("Reverse DNS resolution done",TCPDEBUG);
+ }
+ return cpystr (ret);
+}
+
+/* Validate name
+ * Accepts: domain name
+ * Returns: T if valid, NIL otherwise
+ */
+
+char *tcp_name_valid (char *s)
+{
+ int c;
+ char *ret,*tail;
+ /* must be non-empty and not too long */
+ if ((ret = (s && *s) ? s : NIL) && (tail = ret + NETMAXHOST)) {
+ /* must be alnum, dot, or hyphen */
+ while ((c = *s++) && (s <= tail) &&
+ (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) ||
+ ((c >= '0') && (c <= '9')) || (c == '-') || (c == '.')));
+ if (c) ret = NIL;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/nt/tcp_nt.h b/imap/src/osdep/nt/tcp_nt.h
new file mode 100644
index 00000000..284cb226
--- /dev/null
+++ b/imap/src/osdep/nt/tcp_nt.h
@@ -0,0 +1,51 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Winsock TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+/* TCP input buffer -- must be large enough to prevent overflow */
+
+#define BUFLEN 16384 /* 32768 causes stdin read() to barf */
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#undef ERROR /* quell conflicting definition diagnostic */
+
+
+/* TCP I/O stream (must be before osdep.h is included) */
+
+#define TCPSTREAM struct tcp_stream
+TCPSTREAM {
+ char *host; /* host name */
+ char *remotehost; /* remote host name */
+ unsigned long port; /* port number */
+ char *localhost; /* local host name */
+ SOCKET tcpsi; /* tcp socket */
+ SOCKET tcpso; /* tcp socket */
+ long ictr; /* input counter */
+ char *iptr; /* input pointer */
+ char ibuf[BUFLEN]; /* input buffer */
+};
diff --git a/imap/src/osdep/nt/tenexnt.c b/imap/src/osdep/nt/tenexnt.c
new file mode 100644
index 00000000..4805b7c5
--- /dev/null
+++ b/imap/src/osdep/nt/tenexnt.c
@@ -0,0 +1,1310 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Tenex mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 22 May 1990
+ * Last Edited: 18 June 2007
+ */
+
+
+/* FILE TIME SEMANTICS
+ *
+ * The atime is the last read time of the file.
+ * The mtime is the last flags update time of the file.
+ * The ctime is the last write time of the file.
+ *
+ * TEXT SIZE SEMANTICS
+ *
+ * Most of the text sizes are in internal (LF-only) form, except for the
+ * msg.text size. Beware.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/utime.h>
+#include "misc.h"
+#include "dummy.h"
+
+/* TENEX I/O stream local data */
+
+typedef struct tenex_local {
+ unsigned int shouldcheck: 1; /* if ping should do a check instead */
+ unsigned int mustcheck: 1; /* if ping must do a check instead */
+ int fd; /* file descriptor for I/O */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* local snarf time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned long uid; /* current text uid */
+ SIZEDTEXT text; /* current text */
+} TENEXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((TENEXLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *tenex_valid (char *name);
+int tenex_isvalid (char *name,char *file);
+void *tenex_parameters (long function,void *value);
+void tenex_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void tenex_list (MAILSTREAM *stream,char *ref,char *pat);
+void tenex_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long tenex_create (MAILSTREAM *stream,char *mailbox);
+long tenex_delete (MAILSTREAM *stream,char *mailbox);
+long tenex_rename (MAILSTREAM *stream,char *old,char *newname);
+long tenex_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *tenex_open (MAILSTREAM *stream);
+void tenex_close (MAILSTREAM *stream,long options);
+void tenex_fast (MAILSTREAM *stream,char *sequence,long flags);
+void tenex_flags (MAILSTREAM *stream,char *sequence,long flags);
+char *tenex_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long tenex_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void tenex_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void tenex_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long tenex_ping (MAILSTREAM *stream);
+void tenex_check (MAILSTREAM *stream);
+void tenex_snarf (MAILSTREAM *stream);
+long tenex_expunge (MAILSTREAM *stream,char *sequence,long options);
+long tenex_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long tenex_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+unsigned long tenex_size (MAILSTREAM *stream,unsigned long m);
+long tenex_parse (MAILSTREAM *stream);
+MESSAGECACHE *tenex_elt (MAILSTREAM *stream,unsigned long msgno);
+void tenex_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
+void tenex_update_status (MAILSTREAM *stream,unsigned long msgno,
+ long syncflag);
+unsigned long tenex_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size);
+
+
+/* Tenex mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER tenexdriver = {
+ "tenex", /* driver name */
+ DR_LOCAL|DR_MAIL, /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ tenex_valid, /* mailbox is valid for us */
+ tenex_parameters, /* manipulate parameters */
+ tenex_scan, /* scan mailboxes */
+ tenex_list, /* list mailboxes */
+ tenex_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ tenex_create, /* create mailbox */
+ tenex_delete, /* delete mailbox */
+ tenex_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ tenex_open, /* open mailbox */
+ tenex_close, /* close mailbox */
+ tenex_flags, /* fetch message "fast" attributes */
+ tenex_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ tenex_header, /* fetch message header */
+ tenex_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ tenex_flag, /* modify flags */
+ tenex_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ tenex_ping, /* ping mailbox to see if still alive */
+ tenex_check, /* check for new messages */
+ tenex_expunge, /* expunge deleted messages */
+ tenex_copy, /* copy messages to another mailbox */
+ tenex_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM tenexproto = {&tenexdriver};
+
+/* Tenex mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *tenex_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return tenex_isvalid (name,tmp) ? &tenexdriver : NIL;
+}
+
+
+/* Tenex mail test for valid mailbox
+ * Accepts: mailbox name
+ * buffer to return file name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int tenex_isvalid (char *name,char *file)
+{
+ int fd;
+ int ret = NIL;
+ char *s,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ struct utimbuf times;
+ errno = EINVAL; /* assume invalid argument */
+ /* if file, get its status */
+ if ((s = dummy_file (file,name)) && !stat (s,&sbuf) &&
+ ((sbuf.st_mode & S_IFMT) == S_IFREG)) {
+ if (!sbuf.st_size)errno = 0;/* empty file */
+ else if ((fd = open (file,O_BINARY|O_RDONLY,NIL)) >= 0) {
+ memset (tmp,'\0',MAILTMPLEN);
+ if ((read (fd,tmp,64) >= 0) && (s = strchr (tmp,'\012')) &&
+ (s[-1] != '\015')) { /* valid format? */
+ *s = '\0'; /* tie off header */
+ /* must begin with dd-mmm-yy" */
+ ret = (((tmp[2] == '-' && tmp[6] == '-') ||
+ (tmp[1] == '-' && tmp[5] == '-')) &&
+ (s = strchr (tmp+18,',')) && strchr (s+2,';')) ? T : NIL;
+ }
+ else errno = -1; /* bogus format */
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if (sbuf.st_ctime > sbuf.st_atime) {
+ /* preserve atime and mtime */
+ times.actime = sbuf.st_atime;
+ times.modtime = sbuf.st_mtime;
+ utime (file,&times); /* set the times */
+ }
+ }
+ }
+ /* in case INBOX but not tenex format */
+ else if ((errno == ENOENT) && !compare_cstring (name,"INBOX")) errno = -1;
+ return ret; /* return what we should */
+}
+
+
+/* Tenex manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tenex_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* Tenex mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void tenex_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* Tenex mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void tenex_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* Tenex mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void tenex_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* Tenex mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long tenex_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,mbx[MAILTMPLEN];
+ if (s = dummy_file (mbx,mailbox)) return dummy_create (stream,s);
+ sprintf (mbx,"Can't create %.80s: invalid name",mailbox);
+ mm_log (mbx,ERROR);
+ return NIL;
+}
+
+
+/* Tenex mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long tenex_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return tenex_rename (stream,mailbox,NIL);
+}
+
+/* Tenex mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long tenex_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = LONGT;
+ char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ if (!dummy_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'\\')) && !s[1])))) {
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ else if ((fd = open (file,O_BINARY|O_RDWR,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get exclusive parse/append permission */
+ if ((ld = lockname (lock,file,LOCK_EX)) < 0) {
+ mm_log ("Unable to lock rename mailbox",ERROR);
+ return NIL;
+ }
+ /* lock out other users */
+ if (flock (fd,LOCK_EX|LOCK_NB)) {
+ close (fd); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ mm_log (tmp,ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return NIL;
+ }
+
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if ((s = strrchr (tmp,'\\')) && (s != tmp) &&
+ ((tmp[1] != ':') || (s != tmp + 2))) {
+ c = s[1]; /* remember character after delimiter */
+ *s = s[1] = '\0'; /* tie off name at delimiter */
+ /* name doesn't exist, create it */
+ if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
+ *s = '\\'; /* restore delimiter */
+ if (!dummy_create (stream,tmp)) ret = NIL;
+ }
+ else *s = '\\'; /* restore delimiter */
+ s[1] = c; /* restore character after delimiter */
+ }
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* pacify NTFS */
+ /* rename the file */
+ if (ret && rename (file,tmp)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ else {
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* pacify NTFS */
+ if (unlink (file)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %.80s",old,strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return ret; /* return success */
+}
+
+/* Tenex mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *tenex_open (MAILSTREAM *stream)
+{
+ int fd,ld;
+ char tmp[MAILTMPLEN];
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &tenexproto;
+ if (stream->local) fatal ("tenex recycle stream");
+ /* canonicalize the mailbox name */
+ if (!dummy_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ }
+ if (stream->rdonly ||
+ (fd = open (tmp,O_BINARY|O_RDWR,NIL)) < 0) {
+ if ((fd = open (tmp,O_BINARY|O_RDONLY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox: %.80s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ else if (!stream->rdonly) { /* got it, but readonly */
+ mm_log ("Can't get write access to mailbox, access is readonly",WARN);
+ stream->rdonly = T;
+ }
+ }
+ stream->local = fs_get (sizeof (TENEXLOCAL));
+ LOCAL->fd = fd; /* bind the file */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE);
+ LOCAL->text.size = CHUNKSIZE - 1;
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ /* get shared parse permission */
+ if ((ld = lockname (tmp,stream->mailbox,LOCK_SH)) < 0) {
+ mm_log ("Unable to lock open mailbox",ERROR);
+ return NIL;
+ }
+ flock (LOCAL->fd,LOCK_SH); /* lock the file */
+ unlockfd (ld,tmp); /* release shared parse permission */
+ LOCAL->filesize = 0; /* initialize parsed file size */
+ LOCAL->filetime = 0; /* time not set up yet */
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ stream->sequence++; /* bump sequence number */
+ stream->uid_validity = (unsigned long) time (0);
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (tenex_ping (stream) && !stream->nmsgs)
+ mm_log ("Mailbox is empty",(long) NIL);
+ if (!LOCAL) return NIL; /* failure if stream died */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft =
+ stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ return stream; /* return stream to caller */
+}
+
+/* Tenex mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void tenex_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ if (options & CL_EXPUNGE) tenex_expunge (stream,NIL,NIL);
+ stream->silent = silent; /* restore previous status */
+ flock (LOCAL->fd,LOCK_UN); /* unlock local file */
+ close (LOCAL->fd); /* close the local file */
+ /* free local text buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* Tenex mail fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ * Sniffs at file to get flags
+ */
+
+void tenex_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ STRING bs;
+ MESSAGECACHE *elt;
+ unsigned long i;
+ if (stream && LOCAL &&
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ if (!elt->rfc822_size) { /* have header size yet? */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.special.text.size,L_SET);
+ /* resize bigbuf if necessary */
+ if (LOCAL->buflen < elt->private.msg.full.text.size) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buflen = elt->private.msg.full.text.size;
+ LOCAL->buf = (char *) fs_get (LOCAL->buflen + 1);
+ }
+ /* tie off string */
+ LOCAL->buf[elt->private.msg.full.text.size] = '\0';
+ /* read in the message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.full.text.size);
+ INIT (&bs,mail_string,(void *) LOCAL->buf,
+ elt->private.msg.full.text.size);
+ /* calculate its CRLF size */
+ elt->rfc822_size = unix_crlflen (&bs);
+ }
+ tenex_elt (stream,i); /* get current flags from file */
+ }
+}
+
+/* TENEX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *tenex_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ char *s;
+ unsigned long i;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get to header position */
+ lseek (LOCAL->fd,tenex_hdrpos (stream,msgno,&i),L_SET);
+ if (flags & FT_INTERNAL) {
+ if (i > LOCAL->buflen) { /* resize if not enough space */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get (LOCAL->buflen = i + 1);
+ }
+ /* slurp the data */
+ read (LOCAL->fd,LOCAL->buf,*length = i);
+ }
+ else {
+ s = (char *) fs_get (i + 1);/* get readin buffer */
+ s[i] = '\0'; /* tie off string */
+ read (LOCAL->fd,s,i); /* slurp the data */
+ /* make CRLF copy of string */
+ *length = unix_crlfcpy (&LOCAL->buf,&LOCAL->buflen,s,i);
+ fs_give ((void **) &s); /* free readin buffer */
+ }
+ return LOCAL->buf;
+}
+
+/* TENEX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T, always
+ */
+
+long tenex_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ char *s;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ /* get message status */
+ elt = tenex_elt (stream,msgno);
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ elt->seen = T; /* mark message as seen */
+ /* recalculate status */
+ tenex_update_status (stream,msgno,T);
+ mm_flags (stream,msgno);
+ }
+ if (flags & FT_INTERNAL) { /* if internal representation wanted */
+ /* find header position */
+ i = tenex_hdrpos (stream,msgno,&j);
+ if (i > LOCAL->buflen) { /* resize if not enough space */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get (LOCAL->buflen = i + 1);
+ }
+ /* go to text position */
+ lseek (LOCAL->fd,i + j,L_SET);
+ /* slurp the data */
+ if (read (LOCAL->fd,LOCAL->buf,i) != (long) i) return NIL;
+ /* set up stringstruct for internal */
+ INIT (bs,mail_string,LOCAL->buf,i);
+ }
+ else { /* normal form, previous text cached? */
+ if (elt->private.uid == LOCAL->uid)
+ i = elt->private.msg.text.text.size;
+ else { /* not cached, cache it now */
+ LOCAL->uid = elt->private.uid;
+ /* find header position */
+ i = tenex_hdrpos (stream,msgno,&j);
+ /* go to text position */
+ lseek (LOCAL->fd,i + j,L_SET);
+ s = (char *) fs_get ((i = tenex_size (stream,msgno) - j) + 1);
+ s[i] = '\0'; /* tie off string */
+ read (LOCAL->fd,s,i); /* slurp the data */
+ /* make CRLF copy of string */
+ i = elt->private.msg.text.text.size =
+ strcrlfcpy (&LOCAL->text.data,&LOCAL->text.size,s,i);
+ fs_give ((void **) &s); /* free readin buffer */
+ }
+ /* set up stringstruct */
+ INIT (bs,mail_string,LOCAL->text.data,i);
+ }
+ return T; /* success */
+}
+
+/* Tenex mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ */
+
+void tenex_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ struct utimbuf times;
+ struct stat sbuf;
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* make sure read comes after all that */
+ utime (stream->mailbox,&times);
+ }
+}
+
+
+/* Tenex mail per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void tenex_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ struct stat sbuf;
+ /* maybe need to do a checkpoint? */
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ LOCAL->filetime = 0; /* don't do this test for any other messages */
+ }
+ /* recalculate status */
+ tenex_update_status (stream,elt->msgno,NIL);
+}
+
+/* Tenex mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long tenex_ping (MAILSTREAM *stream)
+{
+ unsigned long i = 1;
+ long r = T;
+ int ld;
+ char lock[MAILTMPLEN];
+ struct stat sbuf;
+ if (stream && LOCAL) { /* only if stream already open */
+ fstat (LOCAL->fd,&sbuf); /* get current file poop */
+ if (LOCAL->filetime && !(LOCAL->mustcheck || LOCAL->shouldcheck) &&
+ (LOCAL->filetime < sbuf.st_mtime)) LOCAL->shouldcheck = T;
+ /* check for changed message status */
+ if (LOCAL->mustcheck || LOCAL->shouldcheck) {
+ LOCAL->filetime = sbuf.st_mtime;
+ if (LOCAL->shouldcheck) /* babble when we do this unilaterally */
+ mm_notify (stream,"[CHECK] Checking for flag updates",NIL);
+ while (i <= stream->nmsgs) tenex_elt (stream,i++);
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ }
+ /* get shared parse/append permission */
+ if ((sbuf.st_size != LOCAL->filesize) &&
+ ((ld = lockname (lock,stream->mailbox,LOCK_SH)) >= 0)) {
+ /* parse resulting mailbox */
+ r = (tenex_parse (stream)) ? T : NIL;
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ }
+ return r; /* return result of the parse */
+}
+
+
+/* Tenex mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void tenex_check (MAILSTREAM *stream)
+{
+ /* mark that a check is desired */
+ if (LOCAL) LOCAL->mustcheck = T;
+ if (tenex_ping (stream)) mm_log ("Check completed",(long) NIL);
+}
+
+/* Tenex mail expunge mailbox
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long tenex_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ struct utimbuf times;
+ struct stat sbuf;
+ off_t pos = 0;
+ int ld;
+ unsigned long i = 1;
+ unsigned long j,k,m,recent;
+ unsigned long n = 0;
+ unsigned long delta = 0;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ if (!(ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ tenex_ping (stream))); /* parse sequence if given, ping stream */
+ else if (stream->rdonly) mm_log ("Expunge ignored on readonly mailbox",WARN);
+ else {
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ }
+ /* get exclusive access */
+ if ((ld = lockname (lock,stream->mailbox,LOCK_EX)) < 0)
+ mm_log ("Unable to lock expunge mailbox",ERROR);
+ /* make sure see any newly-arrived messages */
+ else if (!tenex_parse (stream));
+ /* get exclusive access */
+ else if (flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
+ flock (LOCAL->fd,LOCK_SH);/* recover previous lock */
+ mm_log ("Can't expunge because mailbox is in use by another process",
+ ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+
+ else {
+ mm_critical (stream); /* go critical */
+ recent = stream->recent; /* get recent now that pinged and locked */
+ /* for each message */
+ while (i <= stream->nmsgs) {
+ /* get cache element */
+ elt = tenex_elt (stream,i);
+ /* number of bytes to smash or preserve */
+ k = elt->private.special.text.size + tenex_size (stream,i);
+ /* if need to expunge this message */
+ if (elt->deleted && (sequence ? elt->sequence : T)) {
+ /* if recent, note one less recent message */
+ if (elt->recent) --recent;
+ delta += k; /* number of bytes to delete */
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else if (i++ && delta) {/* preserved message */
+ /* first byte to preserve */
+ j = elt->private.special.offset;
+ do { /* read from source position */
+ m = min (k,LOCAL->buflen);
+ lseek (LOCAL->fd,j,L_SET);
+ read (LOCAL->fd,LOCAL->buf,m);
+ pos = j - delta; /* write to destination position */
+ while (T) {
+ lseek (LOCAL->fd,pos,L_SET);
+ if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+ pos += m; /* new position */
+ j += m; /* next chunk, perhaps */
+ } while (k -= m); /* until done */
+ /* note the new address of this text */
+ elt->private.special.offset -= delta;
+ }
+ /* preserved but no deleted messages */
+ else pos = elt->private.special.offset + k;
+ }
+
+ if (n) { /* truncate file after last message */
+ if (pos != (LOCAL->filesize -= delta)) {
+ sprintf (LOCAL->buf,
+ "Calculated size mismatch %lu != %lu, delta = %lu",
+ (unsigned long) pos,(unsigned long) LOCAL->filesize,delta);
+ mm_log (LOCAL->buf,WARN);
+ LOCAL->filesize = pos;/* fix it then */
+ }
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ sprintf (LOCAL->buf,"Expunged %lu messages",n);
+ /* output the news */
+ mm_log (LOCAL->buf,(long) NIL);
+ }
+ else mm_log ("No messages deleted, so no update needed",(long) NIL);
+ fsync (LOCAL->fd); /* force disk update */
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* reset atime to now */
+ utime (stream->mailbox,&times);
+ mm_nocritical (stream); /* release critical */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ flock (LOCAL->fd,LOCK_SH);/* allow sharers again */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+ }
+ return ret;
+}
+
+/* Tenex mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long tenex_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ struct utimbuf times;
+ MESSAGECACHE *elt;
+ unsigned long i,j,k;
+ long ret = LONGT;
+ int fd,ld;
+ char file[MAILTMPLEN],lock[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ /* make sure valid mailbox */
+ if (!tenex_isvalid (mailbox,file)) switch (errno) {
+ case ENOENT: /* no such file? */
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid Tenex-format mailbox name: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a Tenex-format mailbox: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* got file? */
+ if ((fd = open (file,O_BINARY|O_RDWR|O_CREAT,S_IREAD|S_IWRITE)) < 0) {
+ sprintf (LOCAL->buf,"Unable to open copy mailbox: %.80s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ mm_critical (stream); /* go critical */
+ /* get exclusive parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockname (lock,file,LOCK_EX)) < 0)) {
+ mm_log ("Unable to lock copy mailbox",ERROR);
+ mm_nocritical (stream);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
+
+ /* for each requested message */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ /* number of bytes to copy */
+ k = elt->private.special.text.size + tenex_size (stream,i);
+ do { /* read from source position */
+ j = min (k,LOCAL->buflen);
+ read (LOCAL->fd,LOCAL->buf,j);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ } while (ret && (k -= j));/* until done */
+ }
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE)) {
+ sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ }
+ /* set atime to now-1 if successful copy */
+ if (ret) times.actime = time (0) - 1;
+ /* else preserved \Marked status */
+ else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
+ sbuf.st_atime : time (0);
+ times.modtime = sbuf.st_mtime;/* preserve mtime */
+ utime (file,&times); /* set the times */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ close (fd); /* close the file */
+ mm_nocritical (stream); /* release critical */
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE)) {
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = tenex_elt (stream,i))->sequence) {
+ elt->deleted = T; /* mark message deleted */
+ /* recalculate status */
+ tenex_update_status (stream,i,NIL);
+ }
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* make sure atime remains greater */
+ utime (stream->mailbox,&times);
+ }
+ }
+ if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
+ mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN);
+ return ret;
+}
+
+/* Tenex mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long tenex_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd,ld,c;
+ char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ struct utimbuf times;
+ FILE *df;
+ MESSAGECACHE elt;
+ long f;
+ unsigned long i,j,uf,size;
+ STRING *message;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = &tenexproto;
+ /* make sure valid mailbox */
+ if (!tenex_isvalid (mailbox,file)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!compare_cstring (mailbox,"INBOX")) tenex_create (NIL,"INBOX");
+ else {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* falls through */
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid TENEX-format mailbox name: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a TENEX-format mailbox: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get first message */
+ if (!(*af) (stream,data,&flags,&date,&message)) return NIL;
+
+ /* open destination mailbox */
+ if (((fd = open (file,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IREAD|S_IWRITE))
+ < 0) || !(df = fdopen (fd,"ab"))) {
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockname (lock,file,LOCK_EX)) < 0)) {
+ mm_log ("Unable to lock append mailbox",ERROR);
+ close (fd);
+ return NIL;
+ }
+ mm_critical (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ errno = 0;
+ do { /* parse flags */
+ if (!SIZE (message)) { /* guard against zero-length */
+ mm_log ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ f = mail_parse_flags (stream,flags,&i);
+ /* reverse bits (dontcha wish we had CIRC?) */
+ for (uf = 0; i; uf |= 1 << (29 - find_rightmost_bit (&i)));
+ if (date) { /* parse date if given */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ mm_log (tmp,ERROR);
+ ret = NIL; /* mark failure */
+ break;
+ }
+ mail_date (tmp,&elt); /* write preseved date */
+ }
+ else internal_date (tmp); /* get current date in IMAP format */
+ i = GETPOS (message); /* remember current position */
+ for (j = SIZE (message), size = 0; j; --j)
+ if (SNX (message) != '\015') ++size;
+ SETPOS (message,i); /* restore position */
+ /* write header */
+ if (fprintf (df,"%s,%lu;%010lo%02lo\n",tmp,size,uf,(unsigned long) f) < 0)
+ ret = NIL;
+ else { /* write message */
+ while (size) if ((c = 0xff & SNX (message)) != '\015') {
+ if (putc (c,df) != EOF) --size;
+ else break;
+ }
+ /* get next message */
+ if (size || !(*af) (stream,data,&flags,&date,&message)) ret = NIL;
+ }
+ } while (ret && message);
+ /* if error... */
+ if (!ret || (fflush (df) == EOF)) {
+ ftruncate (fd,sbuf.st_size);/* revert file */
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ if (errno) {
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+ ret = NIL;
+ }
+ if (ret) times.actime = time (0) - 1;
+ /* else preserved \Marked status */
+ else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
+ sbuf.st_atime : time (0);
+ times.modtime = sbuf.st_mtime;/* preserve mtime */
+ utime (file,&times); /* set the times */
+ fclose (df); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ mm_nocritical (stream); /* release critical */
+ if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
+ mm_log ("Can not return meaningful APPENDUID with this mailbox format",
+ WARN);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* Tenex mail return internal message size in bytes
+ * Accepts: MAIL stream
+ * message #
+ * Returns: internal size of message
+ */
+
+unsigned long tenex_size (MAILSTREAM *stream,unsigned long m)
+{
+ MESSAGECACHE *elt = mail_elt (stream,m);
+ return ((m < stream->nmsgs) ? mail_elt (stream,m+1)->private.special.offset :
+ LOCAL->filesize) -
+ (elt->private.special.offset + elt->private.special.text.size);
+}
+
+/* Tenex mail parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long tenex_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = NIL;
+ unsigned char c,*s,*t,*x;
+ char tmp[MAILTMPLEN];
+ unsigned long i,j;
+ long curpos = LOCAL->filesize;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ short added = NIL;
+ short silent = stream->silent;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %ld to %ld!",curpos,sbuf.st_size);
+ mm_log (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ stream->silent = T; /* don't pass up mm_exists() events yet */
+ while (sbuf.st_size - curpos){/* while there is stuff to parse */
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,L_SET);
+ if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
+ sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size,
+ i ? strerror (errno) : "no data read");
+ mm_log (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
+ if (!(s = strchr (LOCAL->buf,'\012'))) {
+ sprintf (tmp,"Unable to find newline at %lu in %lu bytes, text: %s",
+ (unsigned long) curpos,i,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ *s = '\0'; /* tie off header line */
+ i = (s + 1) - LOCAL->buf; /* note start of text offset */
+ if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
+ sprintf (tmp,"Unable to parse internal header at %lu: %s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ *s++ = '\0'; *t++ = '\0'; /* tie off fields */
+
+ added = T; /* note that a new message was added */
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ /* instantiate an elt for this message */
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ elt->private.uid = ++stream->uid_last;
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* in case error */
+ elt->private.special.text.size = 0;
+ /* header size not known yet */
+ elt->private.msg.header.text.size = 0;
+ x = s; /* parse the header components */
+ if (mail_parse_date (elt,LOCAL->buf) &&
+ (elt->private.msg.full.text.size = strtoul (s,(char **) &s,10)) &&
+ (!(s && *s)) && isdigit (t[0]) && isdigit (t[1]) && isdigit (t[2]) &&
+ isdigit (t[3]) && isdigit (t[4]) && isdigit (t[5]) &&
+ isdigit (t[6]) && isdigit (t[7]) && isdigit (t[8]) &&
+ isdigit (t[9]) && isdigit (t[10]) && isdigit (t[11]) && !t[12])
+ elt->private.special.text.size = i;
+ else { /* oops */
+ sprintf (tmp,"Unable to parse internal header elements at %ld: %s,%s;%s",
+ curpos,(char *) LOCAL->buf,(char *) x,(char *) t);
+ mm_log (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ /* make sure didn't run off end of file */
+ if ((curpos += (elt->private.msg.full.text.size + i)) > sbuf.st_size) {
+ sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
+ elt->private.special.offset,(unsigned long) curpos,
+ (unsigned long) sbuf.st_size);
+ mm_log (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ c = t[10]; /* remember first system flags byte */
+ t[10] = '\0'; /* tie off flags */
+ j = strtoul (t,NIL,8); /* get user flags value */
+ t[10] = c; /* restore first system flags byte */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ /* calculate system flags */
+ if ((j = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T;
+ if (j & fDELETED) elt->deleted = T;
+ if (j & fFLAGGED) elt->flagged = T;
+ if (j & fANSWERED) elt->answered = T;
+ if (j & fDRAFT) elt->draft = T;
+ if (!(j & fOLD)) { /* newly arrived message? */
+ elt->recent = T;
+ recent++; /* count up a new recent message */
+ /* mark it as old */
+ tenex_update_status (stream,nmsgs,NIL);
+ }
+ }
+ fsync (LOCAL->fd); /* make sure all the fOLD flags take */
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
+ LOCAL->filetime = sbuf.st_mtime;
+ if (added && !stream->rdonly){/* make sure atime updated */
+ struct utimbuf times;
+ times.actime = time (0);
+ times.modtime = LOCAL->filetime;
+ utime (stream->mailbox,&times);
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return LONGT; /* return the winnage */
+}
+
+/* Tenex get cache element with status updating from file
+ * Accepts: MAIL stream
+ * message number
+ * Returns: cache element
+ */
+
+MESSAGECACHE *tenex_elt (MAILSTREAM *stream,unsigned long msgno)
+{
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
+ old.answered = elt->answered; old.draft = elt->draft;
+ old.user_flags = elt->user_flags;
+ tenex_read_flags (stream,elt);
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
+ (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
+ mm_flags (stream,msgno); /* let top level know */
+ return elt;
+}
+
+
+/* Tenex read flags from file
+ * Accepts: MAIL stream
+ * Returns: cache element
+ */
+
+void tenex_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ unsigned long i,j;
+ /* noop if readonly and have valid flags */
+ if (stream->rdonly && elt->valid) return;
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 13,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,12) < 0) {
+ sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ /* calculate system flags */
+ i = (((LOCAL->buf[10]-'0') * 8) + LOCAL->buf[11]-'0');
+ elt->seen = i & fSEEN ? T : NIL; elt->deleted = i & fDELETED ? T : NIL;
+ elt->flagged = i & fFLAGGED ? T : NIL;
+ elt->answered = i & fANSWERED ? T : NIL; elt->draft = i & fDRAFT ? T : NIL;
+ LOCAL->buf[10] = '\0'; /* tie off flags */
+ j = strtoul(LOCAL->buf,NIL,8);/* get user flags value */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ elt->valid = T; /* have valid flags now */
+}
+
+/* Tenex update status string
+ * Accepts: MAIL stream
+ * message number
+ * flag saying whether or not to sync
+ */
+
+void tenex_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag)
+{
+ struct utimbuf times;
+ struct stat sbuf;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ unsigned long j,k = 0;
+ /* readonly */
+ if (stream->rdonly || !elt->valid) tenex_read_flags (stream,elt);
+ else { /* readwrite */
+ j = elt->user_flags; /* get user flags */
+ /* reverse bits (dontcha wish we had CIRC?) */
+ while (j) k |= 1 << (29 - find_rightmost_bit (&j));
+ /* print new flag string */
+ sprintf (LOCAL->buf,"%010lo%02o",k,(unsigned)
+ (fOLD + (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ while (T) { /* get to that place in the file */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 13,L_SET);
+ /* write new flags */
+ if (write (LOCAL->fd,LOCAL->buf,12) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+ if (syncflag) { /* sync if requested */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* make sure read is later */
+ utime (stream->mailbox,&times);
+ }
+ }
+}
+
+/* Tenex locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * Returns: position of header in file
+ */
+
+unsigned long tenex_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size)
+{
+ unsigned long siz;
+ long i = 0;
+ char c = '\0';
+ char *s = NIL;
+ MESSAGECACHE *elt = tenex_elt (stream,msgno);
+ unsigned long ret = elt->private.special.offset +
+ elt->private.special.text.size;
+ unsigned long msiz = tenex_size (stream,msgno);
+ /* is header size known? */
+ if (!(*size = elt->private.msg.header.text.size)) {
+ lseek (LOCAL->fd,ret,L_SET);/* get to header position */
+ /* search message for LF LF */
+ for (siz = 0; siz < msiz; siz++) {
+ if (--i <= 0) /* read another buffer as necessary */
+ read (LOCAL->fd,s = LOCAL->buf,i = min (msiz-siz,(long) MAILTMPLEN));
+ /* two newline sequence? */
+ if ((c == '\012') && (*s == '\012')) {
+ /* yes, note for later */
+ elt->private.msg.header.text.size = (*size = siz + 1);
+ return ret; /* return to caller */
+ }
+ else c = *s++; /* next character */
+ }
+ /* header consumes entire message */
+ elt->private.msg.header.text.size = *size = msiz;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/nt/unixnt.c b/imap/src/osdep/nt/unixnt.c
new file mode 100644
index 00000000..2e92d39a
--- /dev/null
+++ b/imap/src/osdep/nt/unixnt.c
@@ -0,0 +1,2297 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX mail routines
+ *
+ * Author: Mark Crispin
+ * UW Technology
+ * University of Washington
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 20 December 1989
+ * Last Edited: 27 March 2008
+ */
+
+
+/* DEDICATION
+ *
+ * This file is dedicated to my dog, Unix, also known as Yun-chan and
+ * Unix J. Terwilliker Jehosophat Aloysius Monstrosity Animal Beast. Unix
+ * passed away at the age of 11 1/2 on September 14, 1996, 12:18 PM PDT, after
+ * a two-month bout with cirrhosis of the liver.
+ *
+ * He was a dear friend, and I miss him terribly.
+ *
+ * Lift a leg, Yunie. Luv ya forever!!!!
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/utime.h>
+#include "unixnt.h"
+#include "pseudo.h"
+#include "fdstring.h"
+#include "misc.h"
+#include "dummy.h"
+
+/* UNIX I/O stream local data */
+
+typedef struct unix_local {
+ unsigned int dirty : 1; /* disk copy needs updating */
+ unsigned int ddirty : 1; /* double-dirty, ping becomes checkpoint */
+ unsigned int pseudo : 1; /* uses a pseudo message */
+ unsigned int appending : 1; /* don't mark new messages as old */
+ int fd; /* mailbox file descriptor */
+ int ld; /* lock file descriptor */
+ char *lname; /* lock file name */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned long uid; /* current text uid */
+ SIZEDTEXT text; /* current text */
+ unsigned long textlen; /* current text length */
+ char *line; /* returned line */
+ char *linebuf; /* line readin buffer */
+ unsigned long linebuflen; /* current line readin buffer length */
+} UNIXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((UNIXLOCAL *) stream->local)
+
+
+/* UNIX protected file structure */
+
+typedef struct unix_file {
+ MAILSTREAM *stream; /* current stream */
+ off_t curpos; /* current file position */
+ off_t protect; /* protected position */
+ off_t filepos; /* current last written file position */
+ char *buf; /* overflow buffer */
+ size_t buflen; /* current overflow buffer length */
+ char *bufpos; /* current buffer position */
+} UNIXFILE;
+
+/* Function prototypes */
+
+DRIVER *unix_valid (char *name);
+void *unix_parameters (long function,void *value);
+void unix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void unix_list (MAILSTREAM *stream,char *ref,char *pat);
+void unix_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long unix_create (MAILSTREAM *stream,char *mailbox);
+long unix_delete (MAILSTREAM *stream,char *mailbox);
+long unix_rename (MAILSTREAM *stream,char *old,char *newname);
+MAILSTREAM *unix_open (MAILSTREAM *stream);
+void unix_close (MAILSTREAM *stream,long options);
+char *unix_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long unix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+char *unix_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
+ unsigned long *length,long flags);
+void unix_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long unix_ping (MAILSTREAM *stream);
+void unix_check (MAILSTREAM *stream);
+long unix_expunge (MAILSTREAM *stream,char *sequence,long options);
+long unix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long unix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+int unix_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg);
+int unix_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set);
+
+void unix_abort (MAILSTREAM *stream);
+char *unix_file (char *dst,char *name);
+int unix_lock (char *file,int flags,int mode,char *lock,int op);
+void unix_unlock (int fd,MAILSTREAM *stream,char *lock);
+int unix_parse (MAILSTREAM *stream,char *lock,int op);
+char *unix_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size);
+unsigned long unix_pseudo (MAILSTREAM *stream,char *hdr);
+unsigned long unix_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
+ unsigned long uid,long flag);
+long unix_rewrite (MAILSTREAM *stream,unsigned long *nexp,char *lock,
+ long flags);
+long unix_extend (MAILSTREAM *stream,unsigned long size);
+void unix_write (UNIXFILE *f,char *s,unsigned long i);
+void unix_phys_write (UNIXFILE *f,char *buf,size_t size);
+
+/* UNIX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER unixdriver = {
+ "unix", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_NONEWMAILRONLY|DR_XPOINT,
+ (DRIVER *) NIL, /* next driver */
+ unix_valid, /* mailbox is valid for us */
+ unix_parameters, /* manipulate parameters */
+ unix_scan, /* scan mailboxes */
+ unix_list, /* list mailboxes */
+ unix_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ unix_create, /* create mailbox */
+ unix_delete, /* delete mailbox */
+ unix_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ unix_open, /* open mailbox */
+ unix_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ unix_header, /* fetch message header */
+ unix_text, /* fetch message text */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ unix_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ unix_ping, /* ping mailbox to see if still alive */
+ unix_check, /* check for new messages */
+ unix_expunge, /* expunge deleted messages */
+ unix_copy, /* copy messages to another mailbox */
+ unix_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM unixproto = {&unixdriver};
+
+ /* driver parameters */
+static long unix_fromwidget = T;
+
+/* UNIX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *unix_valid (char *name)
+{
+ int fd;
+ DRIVER *ret = NIL;
+ int c,r;
+ char tmp[MAILTMPLEN],file[MAILTMPLEN],*s,*t;
+ struct stat sbuf;
+ struct utimbuf times;
+ errno = EINVAL; /* assume invalid argument */
+ /* must be non-empty file */
+ if ((t = dummy_file (file,name)) && !stat (t,&sbuf) &&
+ ((sbuf.st_mode & S_IFMT) == S_IFREG)) {
+ if (!sbuf.st_size)errno = 0;/* empty file */
+ else if ((fd = open (file,O_BINARY|O_RDONLY,NIL)) >= 0) {
+ memset (tmp,'\0',MAILTMPLEN);
+ if (read (fd,tmp,MAILTMPLEN-1) <= 0) errno = -1;
+ else { /* ignore leading whitespace */
+ for (s = tmp,c = '\n';
+ (*s == '\r') || (*s == '\n') || (*s == ' ') || (*s == '\t');
+ c = *s++);
+ if (c == '\n') { /* at start of a line? */
+ VALID (s,t,r,c); /* yes, validate format */
+ if (r) ret = &unixdriver;
+ else errno = -1; /* invalid format */
+ }
+ }
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) {
+ /* yes, preserve atime and mtime */
+ times.actime = sbuf.st_atime;
+ times.modtime = sbuf.st_mtime;
+ utime (file,&times); /* set the times */
+ }
+ }
+ }
+ return ret; /* return what we should */
+}
+/* UNIX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *unix_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_FROMWIDGET:
+ unix_fromwidget = (long) value;
+ case GET_FROMWIDGET:
+ ret = (void *) unix_fromwidget;
+ break;
+ }
+ return ret;
+}
+
+/* UNIX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void unix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* UNIX mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void unix_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* UNIX mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void unix_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* UNIX mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long unix_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN];
+ long ret = NIL;
+ int fd;
+ time_t ti = time (0);
+ if (!(s = dummy_file (mbx,mailbox))) {
+ sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
+ mm_log (tmp,ERROR);
+ }
+ /* create underlying file */
+ else if (dummy_create_path (stream,s,NIL)) {
+ if ((s = strrchr (s,'\\')) && !s[1]) ret = T;
+ if ((fd = open (mbx,O_WRONLY|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
+ mm_log (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else { /* initialize header */
+ memset (tmp,'\0',MAILTMPLEN);
+ sprintf (tmp,"From %s %s",pseudo_from,ctime (&ti));
+ if (s = strpbrk (tmp,"\r\n")) *s = '\0';
+ strcat (tmp,"\r\nDate: ");
+ rfc822_fixed_date (s = tmp + strlen (tmp));
+ sprintf (s += strlen (s), /* write the pseudo-header */
+ "\r\nFrom: %s <%s@%s>\r\nSubject: %s\r\nX-IMAP: %010lu 0000000000\r\nStatus: RO\r\n\r\n%s\r\n\r\n",
+ pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
+ (unsigned long) ti,pseudo_msg);
+ if (write (fd,tmp,strlen (tmp)) > 0) {
+ close (fd); /* close file */
+ ret = T;
+ }
+ else {
+ sprintf (tmp,"Can't initialize mailbox node %.80s: %s",mbx,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ close (fd); /* close file before unlinking */
+ unlink (mbx); /* delete the file */
+ }
+ }
+ }
+ return ret;
+}
+
+/* UNIX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long unix_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return unix_rename (stream,mailbox,NIL);
+}
+
+
+/* UNIX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long unix_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = NIL;
+ char c,*s = NIL;
+ char tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN],lockx[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ mm_critical (stream); /* get the c-client lock */
+ if (!dummy_file (file,old) ||
+ (newname && (!(s = dummy_file (tmp,newname)) ||
+ ((s = strrchr (s,'\\')) && !s[1]))))
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ else if ((ld = lockname (lock,file,NIL)) < 0)
+ sprintf (tmp,"Can't get lock for mailbox %.80s",old);
+
+ else { /* lock out other c-clients */
+ if (flock (ld,LOCK_EX|LOCK_NB)) {
+ close (ld); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ }
+ /* lock out non c-client applications */
+ else if ((fd = unix_lock (file,O_BINARY|O_RDWR,S_IREAD|S_IWRITE,lockx,
+ LOCK_EX)) < 0)
+ sprintf (tmp,"Can't lock mailbox %.80s: %s",old,strerror (errno));
+ else {
+ unix_unlock(fd,NIL,lockx);/* pacify evil NTFS */
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if ((s = strrchr (tmp,'\\')) && (s != tmp) &&
+ ((tmp[1] != ':') || (s != tmp + 2))) {
+ c = s[1]; /* remember character after delimiter */
+ *s = s[1] = '\0'; /* tie off name at delimiter */
+ /* name doesn't exist, create it */
+ if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
+ *s = '\\'; /* restore delimiter */
+ if (!dummy_create (stream,newname)) {
+ flock (ld,LOCK_UN);
+ close (ld); /* close c-client lock */
+ unlink (lock); /* and delete it */
+ mm_nocritical (stream);
+ return NIL; /* couldn't create superior */
+ }
+ }
+ else *s = '\\'; /* restore delimiter */
+ s[1] = c; /* restore character after delimiter */
+ }
+ if (rename (file,tmp))
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ else ret = T; /* set success */
+ }
+ else if (unlink (file)) /* want delete */
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ else ret = T; /* set success */
+ flock (ld,LOCK_UN); /* release c-client lock */
+ close (ld); /* close c-client lock */
+ unlink (lock); /* and delete it */
+ }
+ }
+ mm_nocritical (stream); /* no longer critical */
+ if (!ret) mm_log (tmp,ERROR); /* log error */
+ return ret; /* return success or failure */
+}
+
+/* UNIX mail open
+ * Accepts: Stream to open
+ * Returns: Stream on success, NIL on failure
+ */
+
+MAILSTREAM *unix_open (MAILSTREAM *stream)
+{
+ int fd;
+ char tmp[MAILTMPLEN];
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &unixproto;
+ if (stream->local) fatal ("unix recycle stream");
+ stream->local = memset (fs_get (sizeof (UNIXLOCAL)),0,sizeof (UNIXLOCAL));
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ /* canonicalize the stream mailbox name */
+ if (!dummy_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* flush old name */
+ fs_give ((void **) &stream->mailbox);
+ /* save canonical name */
+ stream->mailbox = cpystr (tmp);
+ LOCAL->fd = LOCAL->ld = -1; /* no file or state locking yet */
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = CHUNKSIZE) + 1);
+ LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE);
+ LOCAL->text.size = CHUNKSIZE - 1;
+ LOCAL->linebuf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->linebuflen = CHUNKSIZE - 1;
+ stream->sequence++; /* bump sequence number */
+ if (!stream->rdonly) { /* make lock for read/write access */
+ if ((fd = lockname (tmp,stream->mailbox,NIL)) < 0)
+ mm_log ("Can't open mailbox lock, access is readonly",WARN);
+ /* can get the lock? */
+ else if (flock (fd,LOCK_EX|LOCK_NB)) {
+ if (!stream->silent)
+ mm_log ("Mailbox is open by another process, access is readonly",WARN);
+ close (fd);
+ }
+ else { /* got the lock, nobody else can alter state */
+ LOCAL->ld = fd; /* note lock's fd and name */
+ LOCAL->lname = cpystr (tmp);
+ }
+ }
+
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ /* will we be able to get write access? */
+ if ((LOCAL->ld >= 0) && access (stream->mailbox,02) && (errno == EACCES)) {
+ mm_log ("Can't get write access to mailbox, access is readonly",WARN);
+ flock (LOCAL->ld,LOCK_UN); /* release the lock */
+ close (LOCAL->ld); /* close the lock file */
+ LOCAL->ld = -1; /* no more lock fd */
+ unlink (LOCAL->lname); /* delete it */
+ }
+ /* reset UID validity */
+ stream->uid_validity = stream->uid_last = 0;
+ if (stream->silent && !stream->rdonly && (LOCAL->ld < 0))
+ unix_abort (stream); /* abort if can't get RW silent stream */
+ /* parse mailbox */
+ else if (unix_parse (stream,tmp,LOCK_SH)) {
+ unix_unlock (LOCAL->fd,stream,tmp);
+ mail_unlock (stream);
+ mm_nocritical (stream); /* done with critical */
+ }
+ if (!LOCAL) return NIL; /* failure if stream died */
+ /* make sure upper level knows readonly */
+ stream->rdonly = (LOCAL->ld < 0);
+ /* notify about empty mailbox */
+ if (!(stream->nmsgs || stream->silent)) mm_log ("Mailbox is empty",NIL);
+ if (!stream->rdonly) { /* flags stick if readwrite */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft = T;
+ /* have permanent keywords */
+ stream->perm_user_flags = 0xffffffff;
+ /* and maybe can create them too */
+ stream->kwd_create = stream->user_flags[NUSERFLAGS-1] ? NIL : T;
+ }
+ return stream; /* return stream alive to caller */
+}
+
+
+/* UNIX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void unix_close (MAILSTREAM *stream,long options)
+{
+ int silent = stream->silent;
+ stream->silent = T; /* go silent */
+ /* expunge if requested */
+ if (options & CL_EXPUNGE) unix_expunge (stream,NIL,NIL);
+ /* else dump final checkpoint */
+ else if (LOCAL->dirty) unix_check (stream);
+ stream->silent = silent; /* restore old silence state */
+ unix_abort (stream); /* now punt the file and local data */
+}
+
+/* UNIX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+ /* lines to filter from header */
+static STRINGLIST *unix_hlines = NIL;
+
+char *unix_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned char *s;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get cache */
+ if (!unix_hlines) { /* once only code */
+ STRINGLIST *lines = unix_hlines = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "Status"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-Status"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-Keywords"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-UID"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-IMAP"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-IMAPbase"));
+ }
+ /* go to header position */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.msg.header.offset,L_SET);
+
+ if (flags & FT_INTERNAL) { /* initial data OK? */
+ if (elt->private.msg.header.text.size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
+ elt->private.msg.header.text.size) + 1);
+ }
+ /* read message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.header.text.size);
+ /* got text, tie off string */
+ LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0';
+ }
+ else { /* need to make a CRLF version */
+ read (LOCAL->fd,s = (char *) fs_get (elt->private.msg.header.text.size+1),
+ elt->private.msg.header.text.size);
+ /* tie off string, and convert to CRLF */
+ s[elt->private.msg.header.text.size] = '\0';
+ *length = unix_crlfcpy (&LOCAL->buf,&LOCAL->buflen,s,
+ elt->private.msg.header.text.size);
+ fs_give ((void **) &s); /* free readin buffer */
+ }
+ *length = mail_filter (LOCAL->buf,*length,unix_hlines,FT_NOT);
+ return LOCAL->buf; /* return processed copy */
+}
+
+/* UNIX mail fetch message text
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL if failure
+ */
+
+long unix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ char *s;
+ unsigned long i;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);/* get cache element */
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ /* mark message seen and dirty */
+ elt->seen = elt->private.dirty = LOCAL->dirty = T;
+ mm_flags (stream,msgno);
+ }
+ s = unix_text_work (stream,elt,&i,flags);
+ INIT (bs,mail_string,s,i); /* set up stringstruct */
+ return T; /* success */
+}
+
+/* UNIX mail fetch message text worker routine
+ * Accepts: MAIL stream
+ * message cache element
+ * pointer to returned header text length
+ * option flags
+ */
+
+char *unix_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
+ unsigned long *length,long flags)
+{
+ FDDATA d;
+ STRING bs;
+ unsigned char c,*s,tmp[CHUNKSIZE];
+ /* go to text position */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.msg.text.offset,L_SET);
+ if (flags & FT_INTERNAL) { /* initial data OK? */
+ if (elt->private.msg.text.text.size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
+ elt->private.msg.text.text.size) + 1);
+ }
+ /* read message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size);
+ /* got text, tie off string */
+ LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0';
+ return LOCAL->buf;
+ }
+ /* have it cached already? */
+ if (elt->private.uid != LOCAL->uid) {
+ /* not cached, cache it now */
+ LOCAL->uid = elt->private.uid;
+ /* is buffer big enough? */
+ if (elt->rfc822_size > LOCAL->text.size) {
+ /* excessively conservative, but the right thing is too hard to do */
+ fs_give ((void **) &LOCAL->text.data);
+ LOCAL->text.data = (unsigned char *)
+ fs_get ((LOCAL->text.size = elt->rfc822_size) + 1);
+ }
+ d.fd = LOCAL->fd; /* yes, set up file descriptor */
+ d.pos = elt->private.special.offset + elt->private.msg.text.offset;
+ d.chunk = tmp; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* file chunk size */
+ INIT (&bs,fd_string,&d,elt->private.msg.text.text.size);
+ for (s = (char *) LOCAL->text.data; SIZE (&bs);) switch (c = SNX (&bs)) {
+ case '\r': /* carriage return seen */
+ *s++ = c; /* copy it and any succeeding LF */
+ if (SIZE (&bs) && (CHR (&bs) == '\n')) *s++ = SNX (&bs);
+ break;
+ case '\n':
+ *s++ = '\r'; /* insert a CR */
+ default:
+ *s++ = c; /* copy characters */
+ }
+ *s = '\0'; /* tie off buffer */
+ /* calculate length of cached data */
+ LOCAL->textlen = s - LOCAL->text.data;
+ }
+ *length = LOCAL->textlen; /* return from cache */
+ return (char *) LOCAL->text.data;
+}
+
+/* UNIX per-message modify flag
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void unix_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ /* only after finishing */
+ if (elt->valid) elt->private.dirty = LOCAL->dirty = T;
+}
+
+
+/* UNIX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long unix_ping (MAILSTREAM *stream)
+{
+ char lock[MAILTMPLEN];
+ struct stat sbuf;
+ /* big no-op if not readwrite */
+ if (LOCAL && (LOCAL->ld >= 0) && !stream->lock) {
+ if (stream->rdonly) { /* does he want to give up readwrite? */
+ /* checkpoint if we changed something */
+ if (LOCAL->dirty) unix_check (stream);
+ flock (LOCAL->ld,LOCK_UN);/* release readwrite lock */
+ close (LOCAL->ld); /* close the readwrite lock file */
+ LOCAL->ld = -1; /* no more readwrite lock fd */
+ unlink (LOCAL->lname); /* delete the readwrite lock file */
+ }
+ else { /* get current mailbox size */
+ if (LOCAL->fd >= 0) fstat (LOCAL->fd,&sbuf);
+ else if (stat (stream->mailbox,&sbuf)) {
+ sprintf (LOCAL->buf,"Mailbox stat failed, aborted: %s",
+ strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ unix_abort (stream);
+ return NIL;
+ }
+ /* parse if mailbox changed */
+ if ((LOCAL->ddirty || (sbuf.st_size != LOCAL->filesize)) &&
+ unix_parse (stream,lock,LOCK_EX)) {
+ /* force checkpoint if double-dirty */
+ if (LOCAL->ddirty) unix_rewrite (stream,NIL,lock,NIL);
+ /* unlock mailbox */
+ else unix_unlock (LOCAL->fd,stream,lock);
+ mail_unlock (stream); /* and stream */
+ mm_nocritical (stream); /* done with critical */
+ }
+ }
+ }
+ return LOCAL ? LONGT : NIL; /* return if still alive */
+}
+
+/* UNIX mail check mailbox
+ * Accepts: MAIL stream
+ */
+
+void unix_check (MAILSTREAM *stream)
+{
+ char lock[MAILTMPLEN];
+ /* parse and lock mailbox */
+ if (LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
+ unix_parse (stream,lock,LOCK_EX)) {
+ /* any unsaved changes? */
+ if (LOCAL->dirty && unix_rewrite (stream,NIL,lock,NIL)) {
+ if (!stream->silent) mm_log ("Checkpoint completed",NIL);
+ }
+ /* no checkpoint needed, just unlock */
+ else unix_unlock (LOCAL->fd,stream,lock);
+ mail_unlock (stream); /* unlock the stream */
+ mm_nocritical (stream); /* done with critical */
+ }
+}
+
+
+/* UNIX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long unix_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ unsigned long i;
+ char lock[MAILTMPLEN];
+ char *msg = NIL;
+ /* parse and lock mailbox */
+ if (ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
+ unix_parse (stream,lock,LOCK_EX)) {
+ /* check expunged messages if not dirty */
+ for (i = 1; !LOCAL->dirty && (i <= stream->nmsgs); i++) {
+ MESSAGECACHE *elt = mail_elt (stream,i);
+ if (mail_elt (stream,i)->deleted) LOCAL->dirty = T;
+ }
+ if (!LOCAL->dirty) { /* not dirty and no expunged messages */
+ unix_unlock (LOCAL->fd,stream,lock);
+ msg = "No messages deleted, so no update needed";
+ }
+ else if (unix_rewrite (stream,&i,lock,sequence ? LONGT : NIL)) {
+ if (i) sprintf (msg = LOCAL->buf,"Expunged %lu messages",i);
+ else msg = "Mailbox checkpointed, but no messages expunged";
+ }
+ /* rewrite failed */
+ else unix_unlock (LOCAL->fd,stream,lock);
+ mail_unlock (stream); /* unlock the stream */
+ mm_nocritical (stream); /* done with critical */
+ if (msg && !stream->silent) mm_log (msg,NIL);
+ }
+ else if (!stream->silent) mm_log("Expunge ignored on readonly mailbox",WARN);
+ return ret;
+}
+
+/* UNIX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long unix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ int fd;
+ char *s,file[MAILTMPLEN],lock[MAILTMPLEN];
+ struct utimbuf times;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ long ret = T;
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ copyuid_t cu = (copyuid_t) (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ?
+ NIL : mail_parameters (NIL,GET_COPYUID,NIL));
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ MAILSTREAM *tstream = NIL;
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* make sure destination is valid */
+ if (!(unix_valid (mailbox) || !errno))
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ }
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ unix_create (NIL,"INBOX");/* create empty INBOX */
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid UNIX-format mailbox name: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a UNIX-format mailbox: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+
+ /* try to open rewrite for UIDPLUS */
+ if ((tstream = mail_open_work (&unixdriver,NIL,mailbox,
+ OP_SILENT|OP_NOKOD)) && tstream->rdonly)
+ tstream = mail_close (tstream);
+ if (cu && !tstream) { /* wanted a COPYUID? */
+ sprintf (LOCAL->buf,"Unable to write-open mailbox for COPYUID: %.80s",
+ mailbox);
+ MM_LOG (LOCAL->buf,WARN);
+ cu = NIL; /* don't try to do COPYUID */
+ }
+ LOCAL->buf[0] = '\0';
+ mm_critical (stream); /* go critical */
+ if ((fd = unix_lock (dummy_file (file,mailbox),
+ O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IREAD|S_IWRITE,
+ lock,LOCK_EX)) < 0) {
+ mm_nocritical (stream); /* done with critical */
+ sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR); /* log the error */
+ return NIL; /* failed */
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ /* write all requested messages to mailbox */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
+ if (LOCAL->buf[(j = elt->private.special.text.size) - 2] != '\r') {
+ LOCAL->buf[j - 1] = '\r';
+ LOCAL->buf[j++] = '\n';
+ }
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ else { /* internal header succeeded */
+ s = unix_header (stream,i,&j,NIL);
+ /* header size, sans trailing newline */
+ if (j && (s[j - 4] == '\r')) j -= 2;
+ if (write (fd,s,j) < 0) ret = NIL;
+ else { /* message header succeeded */
+ j = tstream ? /* write UIDPLUS data if have readwrite */
+ unix_xstatus (stream,LOCAL->buf,elt,++(tstream->uid_last),LONGT) :
+ unix_xstatus (stream,LOCAL->buf,elt,NIL,NIL);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ else { /* message status succeeded */
+ s = unix_text_work (stream,elt,&j,NIL);
+ if ((write (fd,s,j) < 0) || (write (fd,"\r\n",2) < 0))
+ ret = NIL;
+ else if (cu) { /* need to pass back new UID? */
+ mail_append_set (source,mail_uid (stream,i));
+ mail_append_set (dest,tstream->uid_last);
+ }
+ }
+ }
+ }
+ }
+
+ if (!ret || fsync (fd)) { /* force out the update */
+ sprintf (LOCAL->buf,"Message copy failed: %s",strerror (errno));
+ ftruncate (fd,sbuf.st_size);
+ ret = NIL;
+ }
+ /* force UIDVALIDITY assignment now */
+ if (tstream && !tstream->uid_validity)
+ tstream->uid_validity = (unsigned long) time (0);
+ /* return sets if doing COPYUID */
+ if (cu && ret) (*cu) (stream,mailbox,tstream->uid_validity,source,dest);
+ else { /* flush any sets we may have built */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ times.modtime = time (0); /* set mtime to now */
+ /* set atime to now-1 if successful copy */
+ if (ret) times.actime = times.modtime - 1;
+
+ else times.actime = /* else preserve \Marked status */
+ ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
+ sbuf.st_atime : times.modtime;
+ utime (file,&times); /* set the times */
+ unix_unlock (fd,NIL,lock); /* unlock and close mailbox */
+ if (tstream) { /* update last UID if we can */
+ UNIXLOCAL * local = (UNIXLOCAL *) tstream->local;
+ local->dirty = T; /* do a rewrite */
+ local->appending = T; /* but not at the cost of marking as old */
+ tstream = mail_close (tstream);
+ }
+ /* log the error */
+ if (!ret) mm_log (LOCAL->buf,ERROR);
+ /* delete if requested message */
+ else if (options & CP_MOVE) for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence)
+ elt->deleted = elt->private.dirty = LOCAL->dirty = T;
+ mm_nocritical (stream); /* release critical */
+ return ret;
+}
+
+/* UNIX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+#define BUFLEN 8*MAILTMPLEN
+
+long unix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd;
+ unsigned long i;
+ char *flags,*date,buf[BUFLEN],tmp[MAILTMPLEN],file[MAILTMPLEN],
+ lock[MAILTMPLEN];
+ struct utimbuf times;
+ FILE *sf,*df;
+ MESSAGECACHE elt;
+ STRING *message;
+ unsigned long uidlocation = 0;
+ appenduid_t au = (appenduid_t)
+ (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ? NIL :
+ mail_parameters (NIL,GET_APPENDUID,NIL));
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ long ret = LONGT;
+ MAILSTREAM *tstream = NIL;
+ if (!stream) { /* stream specified? */
+ stream = &unixproto; /* no, default stream to prototype */
+ for (i = 0; i < NUSERFLAGS && stream->user_flags[i]; ++i)
+ fs_give ((void **) &stream->user_flags[i]);
+ }
+ if (!unix_valid (mailbox)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!compare_cstring (mailbox,"INBOX")) {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ unix_create (NIL,"INBOX"); /* create empty INBOX */
+ case 0: /* merely empty file? */
+ tstream = stream;
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid UNIX-format mailbox name: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a UNIX-format mailbox: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get sniffing stream for keywords */
+ else if (!(tstream = mail_open (NIL,mailbox,
+ OP_READONLY|OP_SILENT|OP_NOKOD|OP_SNIFF))) {
+ sprintf (tmp,"Unable to examine mailbox for APPEND: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+
+ /* get first message */
+ if (!(*af) (tstream,data,&flags,&date,&message)) return NIL;
+ if (!(sf = tmpfile ())) { /* must have scratch file */
+ sprintf (tmp,".%lx.%lx",(unsigned long) time (0),(unsigned long)getpid ());
+ if (!stat (tmp,&sbuf) || !(sf = fopen (tmp,"wb+"))) {
+ sprintf (tmp,"Unable to create scratch file: %.80s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ unlink (tmp);
+ }
+ do { /* parse date */
+ if (!date) rfc822_date (date = tmp);
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ mm_log (tmp,ERROR);
+ }
+ else { /* user wants to suppress time zones? */
+ if (mail_parameters (NIL,GET_NOTIMEZONES,NIL)) {
+ time_t when = mail_longdate (&elt);
+ date = ctime (&when); /* use traditional date */
+ }
+ /* use POSIX-style date */
+ else date = mail_cdate (tmp,&elt);
+ if (!SIZE (message)) mm_log ("Append of zero-length message",ERROR);
+ else if (!unix_collect_msg (tstream,sf,flags,date,message)) {
+ sprintf (tmp,"Error writing scratch file: %.80s",strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+ /* get next message */
+ else if ((*af) (tstream,data,&flags,&date,&message)) continue;
+ }
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ } while (message); /* until no more messages */
+ if (fflush (sf)) {
+ sprintf (tmp,"Error finishing scratch file: %.80s",strerror (errno));
+ mm_log (tmp,ERROR);
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ }
+ i = ftell (sf); /* size of scratch file */
+
+ /* close sniffing stream */
+ if (tstream != stream) tstream = mail_close (tstream);
+ mm_critical (stream); /* go critical */
+ /* try to open readwrite for UIDPLUS */
+ if ((tstream = mail_open_work (&unixdriver,NIL,mailbox,
+ OP_SILENT|OP_NOKOD)) && tstream->rdonly)
+ tstream = mail_close (tstream);
+ if (au && !tstream) { /* wanted an APPENDUID? */
+ sprintf (tmp,"Unable to re-open mailbox for APPENDUID: %.80s",mailbox);
+ MM_LOG (tmp,WARN);
+ au = NIL;
+ }
+ if (((fd = unix_lock (dummy_file (file,mailbox),
+ O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IREAD|S_IWRITE,
+ lock,LOCK_EX)) < 0) || !(df = fdopen (fd,"ab"))) {
+ mm_nocritical (stream); /* done with critical */
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ rewind (sf);
+ times.modtime = time (0); /* set mtime to now */
+ /* write all messages */
+ if (!unix_append_msgs (tstream,sf,df,au ? dst : NIL) ||
+ (fflush (df) == EOF) || fsync (fd)) {
+ sprintf (buf,"Message append failed: %s",strerror (errno));
+ mm_log (buf,ERROR);
+ ftruncate (fd,sbuf.st_size);/* revert file */
+ times.actime = /* preserve \Marked status */
+ ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
+ sbuf.st_atime : times.modtime;
+ ret = NIL; /* return error */
+ }
+ /* set atime to now-1 if successful copy */
+ else times.actime = times.modtime - 1;
+ utime (file,&times); /* set the times */
+ fclose (sf); /* done with scratch file */
+ /* force UIDVALIDITY assignment now */
+ if (tstream && !tstream->uid_validity)
+ tstream->uid_validity = (unsigned long) time (0);
+ /* return sets if doing APPENDUID */
+ if (au && ret) (*au) (mailbox,tstream->uid_validity,dst);
+ else mail_free_searchset (&dst);
+ flock (fd,LOCK_UN); /* unlock mailbox (can't use unix_unlock() */
+ if (lock && *lock) unlink (lock);
+ fclose (df); /* close mailbox */
+ if (tstream) { /* update last UID if we can */
+ UNIXLOCAL * local = (UNIXLOCAL *) tstream->local;
+ local->dirty = T; /* do a rewrite */
+ local->appending = T; /* but not at the cost of marking as old */
+ tstream = mail_close (tstream);
+ }
+ mm_nocritical (stream); /* release critical */
+ return ret;
+}
+
+/* Collect and write single message to append scratch file
+ * Accepts: MAIL stream
+ * scratch file
+ * flags
+ * date
+ * message stringstruct
+ * Returns: NIL if write error, else T
+ */
+
+int unix_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg)
+{
+ unsigned char *s,*t;
+ unsigned long uf;
+ long f = mail_parse_flags (stream,flags,&uf);
+ /* write metadata */
+ if (fprintf (sf,"%ld %lu ",f,SIZE (msg) + 2) < 0) return NIL;
+ for (s = date; *s; *s++) switch (*s) {
+ default:
+ if (putc (*s,sf) == EOF) return NIL;
+ case '\r': case '\n':
+ break;
+ }
+ if (fputs ("\r\n",sf) == EOF) return NIL;
+ while (uf) /* write user flags */
+ if ((s = stream->user_flags[find_rightmost_bit (&uf)]) &&
+ (fprintf (sf," %s",s) < 0)) return NIL;
+ if (fputs ("\r\n",sf) == EOF) return NIL;
+ while (SIZE (msg)) { /* copy text to scratch file */
+ for (s = (unsigned char *) msg->curpos, t = s + msg->cursize; s < t; ++s)
+ if (!*s) *s = 0x80; /* disallow NUL */
+ /* write buffered text */
+ if (fwrite (msg->curpos,1,msg->cursize,sf) == msg->cursize)
+ SETPOS (msg,GETPOS (msg) + msg->cursize);
+ else return NIL; /* failed */
+ }
+ /* write trailing CRLF and return */
+ return (fputs ("\r\n",sf) == EOF) ? NIL : T;
+}
+
+/* Append messages from scratch file to mailbox
+ * Accepts: MAIL stream
+ * source file
+ * destination file
+ * uidset to update if non-NIL
+ * Returns: T if success, NIL if failure
+ */
+
+int unix_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set)
+{
+ int ti,zn,c;
+ long f;
+ unsigned long i,j;
+ char *x,tmp[MAILTMPLEN];
+ int hdrp = T;
+ /* get message metadata line */
+ while (fgets (tmp,MAILTMPLEN,sf)) {
+ if (!(isdigit (tmp[0]) && strchr (tmp,'\n'))) return NIL;
+ f = strtol (tmp,&x,10); /* get flags */
+ if (!((*x++ == ' ') && isdigit (*x))) return NIL;
+ i = strtoul (x,&x,10); /* get message size */
+ if ((*x++ != ' ') || /* build initial header */
+ (fprintf (df,"From %s@%s %sStatus: ",myusername(),mylocalhost(),x)<0)||
+ (f&fSEEN && (putc ('R',df) == EOF)) ||
+ (fputs ("\r\nX-Status: ",df) == EOF) ||
+ (f&fDELETED && (putc ('D',df) == EOF)) ||
+ (f&fFLAGGED && (putc ('F',df) == EOF)) ||
+ (f&fANSWERED && (putc ('A',df) == EOF)) ||
+ (f&fDRAFT && (putc ('T',df) == EOF)) ||
+ (fputs ("\r\nX-Keywords:",df) == EOF)) return NIL;
+ /* copy keywords */
+ while ((c = getc (sf)) != '\n') switch (c) {
+ case EOF:
+ return NIL;
+ default:
+ if (putc (c,df) == EOF) return NIL;
+ }
+ if ((putc ('\n',df) == EOF) ||
+ (set && (fprintf (df,"X-UID: %lu\r\n",++(stream->uid_last)) < 0)))
+ return NIL;
+
+ for (c = '\n'; i && fgets (tmp,MAILTMPLEN,sf); c = tmp[j-1]) {
+ /* get read line length */
+ if (i < (j = strlen (tmp))) fatal ("unix_append_msgs overrun");
+ i -= j; /* number of bytes left */
+ if (!j) continue; /* do nothing if line emptied */
+ /* complete line? */
+ if ((c == '\n')) switch (tmp[0]) {
+ case 'F': /* possible "From " (case counts here) */
+ if ((j > 4) && (tmp[0] == 'F') && (tmp[1] == 'r') && (tmp[2] == 'o') &&
+ (tmp[3] == 'm') && (tmp[4] == ' ')) {
+ if (!unix_fromwidget) {
+ VALID (tmp,x,ti,zn);/* conditional, only write widget if */
+ if (!ti) break; /* it looks like a valid header */
+ } /* write the widget */
+ if (putc ('>',df) == EOF) return NIL;
+ }
+ break;
+ case 'S': case 's': /* possible "Status:" */
+ if (hdrp && (j > 6) && ((tmp[1] == 't') || (tmp[1] == 'T')) &&
+ ((tmp[2] == 'a') || (tmp[2] == 'A')) &&
+ ((tmp[3] == 't') || (tmp[3] == 'T')) &&
+ ((tmp[4] == 'u') || (tmp[4] == 'U')) &&
+ ((tmp[5] == 's') || (tmp[5] == 'S')) && (tmp[6] == ':') &&
+ (fputs ("X-Original-",df) == EOF)) return NIL;
+ break;
+ case 'X': case 'x': /* possible X-??? header */
+ if (hdrp && (tmp[1] == '-') &&
+ /* possible X-UID: */
+ (((j > 5) && ((tmp[2] == 'U') || (tmp[2] == 'u')) &&
+ ((tmp[3] == 'I') || (tmp[3] == 'i')) &&
+ ((tmp[4] == 'D') || (tmp[4] == 'd')) && (tmp[5] == ':')) ||
+ /* possible X-IMAP: */
+ ((j > 6) && ((tmp[2] == 'I') || (tmp[2] == 'i')) &&
+ ((tmp[3] == 'M') || (tmp[3] == 'm')) &&
+ ((tmp[4] == 'A') || (tmp[4] == 'a')) &&
+ ((tmp[5] == 'P') || (tmp[5] == 'p')) &&
+ ((tmp[6] == ':') ||
+ /* or X-IMAPbase: */
+ ((j > 10) && ((tmp[6] == 'b') || (tmp[6] == 'B')) &&
+ ((tmp[7] == 'a') || (tmp[7] == 'A')) &&
+ ((tmp[8] == 's') || (tmp[8] == 'S')) &&
+ ((tmp[9] == 'e') || (tmp[9] == 'E')) && (tmp[10] == ':')))) ||
+ /* possible X-Status: */
+ ((j > 8) && ((tmp[2] == 'S') || (tmp[2] == 's')) &&
+ ((tmp[3] == 't') || (tmp[3] == 'T')) &&
+ ((tmp[4] == 'a') || (tmp[4] == 'A')) &&
+ ((tmp[5] == 't') || (tmp[5] == 'T')) &&
+ ((tmp[6] == 'u') || (tmp[6] == 'U')) &&
+ ((tmp[7] == 's') || (tmp[7] == 'S')) && (tmp[8] == ':')) ||
+ /* possible X-Keywords: */
+ ((j > 10) && ((tmp[2] == 'K') || (tmp[2] == 'k')) &&
+ ((tmp[3] == 'e') || (tmp[3] == 'E')) &&
+ ((tmp[4] == 'y') || (tmp[4] == 'Y')) &&
+ ((tmp[5] == 'w') || (tmp[5] == 'W')) &&
+ ((tmp[6] == 'o') || (tmp[6] == 'O')) &&
+ ((tmp[7] == 'r') || (tmp[7] == 'R')) &&
+ ((tmp[8] == 'd') || (tmp[8] == 'D')) &&
+ ((tmp[9] == 's') || (tmp[9] == 'S')) && (tmp[10] == ':'))) &&
+ (fputs ("X-Original-",df) == EOF)) return NIL;
+ break;
+ case '\n': /* blank line */
+ hdrp = NIL;
+ break;
+ default: /* nothing to do */
+ break;
+ }
+ /* just write the line */
+ if (fwrite (tmp,1,j,df) != j) return NIL;
+ }
+ if (i) return NIL; /* didn't read entire message */
+ /* update set */
+ if (stream) mail_append_set (set,stream->uid_last);
+ }
+ return T;
+}
+
+/* Internal routines */
+
+
+/* UNIX mail abort stream
+ * Accepts: MAIL stream
+ */
+
+void unix_abort (MAILSTREAM *stream)
+{
+ if (LOCAL) { /* only if a file is open */
+ if (LOCAL->fd >= 0) close (LOCAL->fd);
+ if (LOCAL->ld >= 0) { /* have a mailbox lock? */
+ flock (LOCAL->ld,LOCK_UN);/* yes, release the lock */
+ close (LOCAL->ld); /* close the lock file */
+ unlink (LOCAL->lname); /* and delete it */
+ }
+ if (LOCAL->lname) fs_give ((void **) &LOCAL->lname);
+ /* free local text buffers */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data);
+ if (LOCAL->linebuf) fs_give ((void **) &LOCAL->linebuf);
+ if (LOCAL->line) fs_give ((void **) &LOCAL->line);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* UNIX open and lock mailbox
+ * Accepts: file name to open/lock
+ * file open mode
+ * destination buffer for lock file name
+ * type of locking operation (LOCK_SH or LOCK_EX)
+ */
+
+int unix_lock (char *file,int flags,int mode,char *lock,int op)
+{
+ int fd,ld,j;
+ int i = LOCKTIMEOUT * 60 - 1;
+ char tmp[MAILTMPLEN];
+ time_t t;
+ struct stat sb;
+ sprintf (lock,"%s.lock",file);/* build lock filename */
+ do { /* until OK or out of tries */
+ t = time (0); /* get the time now */
+ /* try to get the lock */
+ if ((ld = open(lock,O_BINARY|O_WRONLY|O_CREAT|O_EXCL,S_IREAD|S_IWRITE))>=0)
+ close (ld); /* got it, close the lock file! */
+ else if (errno != EEXIST) { /* miscellaneous error */
+ sprintf (tmp,"Error creating %.80s: %s",lock,strerror (errno));
+ if (!(i%15)) mm_log (tmp,WARN);
+ }
+ /* lock exists, still active? */
+ else if (!stat (lock,&sb) && (t > sb.st_ctime + LOCKTIMEOUT * 60) &&
+ ((ld = open(lock,O_BINARY|O_WRONLY|O_CREAT,S_IREAD|S_IWRITE))>=0))
+ close (ld); /* got timed-out lock file */
+ else { /* active lock, try again */
+ if (!(i%15)) {
+ sprintf (tmp,"Mailbox %.80s is locked, will override in %d seconds...",
+ file,i);
+ mm_log (tmp,WARN);
+ }
+ sleep (1); /* wait a second before next retry */
+ }
+ } while (*lock && ld < 0 && i--);
+ /* open file */
+ if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
+ else { /* open failed */
+ j = errno; /* preserve error code */
+ if (*lock) unlink (lock); /* flush the lock file if any */
+ errno = j; /* restore error code */
+ }
+ return fd;
+}
+
+/* UNIX unlock and close mailbox
+ * Accepts: file descriptor
+ * (optional) mailbox stream to check atime/mtime
+ * (optional) lock file name
+ */
+
+void unix_unlock (int fd,MAILSTREAM *stream,char *lock)
+{
+ if (stream) { /* need to muck with times? */
+ struct stat sbuf;
+ struct utimbuf times;
+ time_t now = time (0);
+ fstat (fd,&sbuf); /* get file times */
+ if (LOCAL->ld >= 0) { /* yes, readwrite session? */
+ times.actime = now; /* set atime to now */
+ /* set mtime to (now - 1) if necessary */
+ times.modtime = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
+ }
+ else if (stream->recent) { /* readonly with recent messages */
+ if ((sbuf.st_atime >= sbuf.st_mtime) ||
+ (sbuf.st_atime >= sbuf.st_ctime))
+ /* keep past mtime, whack back atime */
+ times.actime = (times.modtime = (sbuf.st_mtime < now) ?
+ sbuf.st_mtime : now) - 1;
+ else now = 0; /* no time change needed */
+ }
+ /* readonly with no recent messages */
+ else if ((sbuf.st_atime < sbuf.st_mtime) ||
+ (sbuf.st_atime < sbuf.st_ctime)) {
+ times.actime = now; /* set atime to now */
+ /* set mtime to (now - 1) if necessary */
+ times.modtime = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
+ }
+ else now = 0; /* no time change needed */
+ /* set the times, note change */
+ if (now && !utime (stream->mailbox,&times))
+ LOCAL->filetime = times.modtime;
+ }
+ flock (fd,LOCK_UN); /* release flock'ers */
+ if (!stream) close (fd); /* close the file if no stream */
+ /* flush the lock file if any */
+ if (lock && *lock) unlink (lock);
+}
+
+/* UNIX mail parse and lock mailbox
+ * Accepts: MAIL stream
+ * space to write lock file name
+ * type of locking operation
+ * Returns: T if parse OK, critical & mailbox is locked shared; NIL if failure
+ */
+
+int unix_parse (MAILSTREAM *stream,char *lock,int op)
+{
+ int zn;
+ unsigned long i,j,k,m;
+ unsigned char c,*s,*t,*u,tmp[MAILTMPLEN],date[30];
+ int ti = 0,retain = T;
+ unsigned long nmsgs = stream->nmsgs;
+ unsigned long prevuid = nmsgs ? mail_elt (stream,nmsgs)->private.uid : 0;
+ unsigned long recent = stream->recent;
+ unsigned long oldnmsgs = stream->nmsgs;
+ short silent = stream->silent;
+ short pseudoseen = NIL;
+ struct stat sbuf;
+ STRING bs;
+ FDDATA d;
+ MESSAGECACHE *elt;
+ mail_lock (stream); /* guard against recursion or pingers */
+ /* toss out previous descriptor */
+ if (LOCAL->fd >= 0) close (LOCAL->fd);
+ mm_critical (stream); /* open and lock mailbox (shared OK) */
+ if ((LOCAL->fd = unix_lock (stream->mailbox,
+ O_BINARY + ((LOCAL->ld >= 0) ? O_RDWR:O_RDONLY),
+ NIL,lock,op)) < 0) {
+ sprintf (tmp,"Mailbox open failed, aborted: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ unix_abort (stream);
+ mail_unlock (stream);
+ mm_nocritical (stream); /* done with critical */
+ return NIL;
+ }
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* validate change in size */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (tmp,"Mailbox shrank from %lu to %lu bytes, aborted",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ mm_log (tmp,ERROR); /* this is pretty bad */
+ unix_unlock (LOCAL->fd,stream,lock);
+ unix_abort (stream);
+ mail_unlock (stream);
+ mm_nocritical (stream); /* done with critical */
+ return NIL;
+ }
+
+ /* new data? */
+ else if (i = sbuf.st_size - LOCAL->filesize) {
+ d.fd = LOCAL->fd; /* yes, set up file descriptor */
+ d.pos = LOCAL->filesize; /* get to that position in the file */
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* file chunk size */
+ INIT (&bs,fd_string,&d,i); /* initialize stringstruct */
+ /* skip leading whitespace for broken MTAs */
+ while (((c = CHR (&bs)) == '\n') || (c == '\r') ||
+ (c == ' ') || (c == '\t')) SNX (&bs);
+ if (SIZE (&bs)) { /* read new data */
+ /* remember internal header position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ s = unix_mbxline (stream,&bs,&i);
+ t = NIL,zn = 0;
+ if (i) VALID (s,t,ti,zn); /* see if valid From line */
+ if (!ti) { /* someone pulled the rug from under us */
+ sprintf (tmp,"Unexpected changes to mailbox (try restarting): %.20s",
+ (char *) s);
+ mm_log (tmp,ERROR);
+ unix_unlock (LOCAL->fd,stream,lock);
+ unix_abort (stream);
+ mail_unlock (stream);
+ mm_nocritical (stream); /* done with critical */
+ return NIL;
+ }
+ stream->silent = T; /* quell main program new message events */
+ do { /* found a message */
+ /* instantiate first new message */
+ mail_exists (stream,++nmsgs);
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ recent++; /* assume recent by default */
+ elt->recent = T;
+ /* note position/size of internal header */
+ elt->private.special.offset = j;
+ elt->private.msg.header.offset = elt->private.special.text.size = i;
+
+ /* generate plausible IMAPish date string */
+ date[2] = date[6] = date[20] = '-'; date[11] = ' ';
+ date[14] = date[17] = ':';
+ /* dd */
+ date[0] = t[ti - 2]; date[1] = t[ti - 1];
+ /* mmm */
+ date[3] = t[ti - 6]; date[4] = t[ti - 5]; date[5] = t[ti - 4];
+ /* hh */
+ date[12] = t[ti + 1]; date[13] = t[ti + 2];
+ /* mm */
+ date[15] = t[ti + 4]; date[16] = t[ti + 5];
+ if (t[ti += 6] == ':') {/* ss */
+ date[18] = t[++ti]; date[19] = t[++ti];
+ ti++; /* move to space */
+ }
+ else date[18] = date[19] = '0';
+ /* yy -- advance over timezone if necessary */
+ if (zn == ti) ti += (((t[zn+1] == '+') || (t[zn+1] == '-')) ? 6 : 4);
+ date[7] = t[ti + 1]; date[8] = t[ti + 2];
+ date[9] = t[ti + 3]; date[10] = t[ti + 4];
+ /* zzz */
+ t = zn ? (t + zn + 1) : (unsigned char *) "LCL";
+ date[21] = *t++; date[22] = *t++; date[23] = *t++;
+ if ((date[21] != '+') && (date[21] != '-')) date[24] = '\0';
+ else { /* numeric time zone */
+ date[24] = *t++; date[25] = *t++;
+ date[26] = '\0'; date[20] = ' ';
+ }
+ /* set internal date */
+ if (!mail_parse_date (elt,date)) {
+ sprintf (tmp,"Unable to parse internal date: %s",(char *) date);
+ mm_log (tmp,WARN);
+ }
+
+ do { /* look for message body */
+ s = t = unix_mbxline (stream,&bs,&i);
+ if (i) switch (*s) { /* check header lines */
+ case 'X': /* possible X-???: line */
+ if (s[1] == '-') { /* must be immediately followed by hyphen */
+ /* X-Status: becomes Status: in S case */
+ if (s[2] == 'S' && s[3] == 't' && s[4] == 'a' && s[5] == 't' &&
+ s[6] == 'u' && s[7] == 's' && s[8] == ':') s += 2;
+ /* possible X-Keywords */
+ else if (s[2] == 'K' && s[3] == 'e' && s[4] == 'y' &&
+ s[5] == 'w' && s[6] == 'o' && s[7] == 'r' &&
+ s[8] == 'd' && s[9] == 's' && s[10] == ':') {
+ SIZEDTEXT uf;
+ retain = NIL; /* don't retain continuation */
+ s += 11; /* flush leading whitespace */
+ while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n'))){
+ while (*s == ' ') s++;
+ /* find end of keyword */
+ if (!(u = strpbrk (s," \n\r"))) u = s + strlen (s);
+ /* got a keyword? */
+ if ((k = (u - s)) && (k <= MAXUSERFLAG)) {
+ uf.data = (unsigned char *) s;
+ uf.size = k;
+ for (j = 0; (j < NUSERFLAGS) && stream->user_flags[j]; ++j)
+ if (!compare_csizedtext (stream->user_flags[j],&uf)) {
+ elt->user_flags |= ((long) 1) << j;
+ break;
+ }
+ }
+ s = u; /* advance to next keyword */
+ }
+ break;
+ }
+
+ /* possible X-IMAP */
+ else if ((s[2] == 'I') && (s[3] == 'M') && (s[4] == 'A') &&
+ (s[5] == 'P') && ((m = (s[6] == ':')) ||
+ ((s[6] == 'b') && (s[7] == 'a') &&
+ (s[8] == 's') && (s[9] == 'e') &&
+ (s[10] == ':')))) {
+ retain = NIL; /* don't retain continuation */
+ if ((nmsgs == 1) && !stream->uid_validity) {
+ /* advance to data */
+ s += m ? 7 : 11;
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ j = 0; /* slurp UID validity */
+ /* found a digit? */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ /* must have valid UID validity and UID last */
+ if (j && isdigit (*s)) {
+ /* pseudo-header seen if X-IMAP */
+ if (m) pseudoseen = LOCAL->pseudo = T;
+ /* save UID validity */
+ stream->uid_validity = j;
+ j = 0; /* slurp UID last */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* save UID last */
+ stream->uid_last = j;
+ /* process keywords */
+ for (j = 0; (*s != '\n') && ((*s != '\r')||(s[1] != '\n'));
+ s = u,j++) {
+ /* flush leading whitespace */
+ while (*s == ' ') s++;
+ u = strpbrk (s," \n\r");
+ /* got a keyword? */
+ if ((j < NUSERFLAGS) && (k = (u - s)) &&
+ (k <= MAXUSERFLAG)) {
+ if (stream->user_flags[j])
+ fs_give ((void **) &stream->user_flags[j]);
+ stream->user_flags[j] = (char *) fs_get (k + 1);
+ strncpy (stream->user_flags[j],s,k);
+ stream->user_flags[j][k] = '\0';
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ /* possible X-UID */
+ else if (s[2] == 'U' && s[3] == 'I' && s[4] == 'D' &&
+ s[5] == ':') {
+ retain = NIL; /* don't retain continuation */
+ /* only believe if have a UID validity */
+ if (stream->uid_validity && ((nmsgs > 1) || !pseudoseen)) {
+ s += 6; /* advance to UID value */
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ j = 0;
+ /* found a digit? */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* flush remainder of line */
+ while (*s != '\n') s++;
+ /* make sure not duplicated */
+ if (elt->private.uid)
+ sprintf (tmp,"Message %lu UID %lu already has UID %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,elt->private.uid);
+ /* make sure UID doesn't go backwards */
+ else if (j <= prevuid)
+ sprintf (tmp,"Message %lu UID %lu less than %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,prevuid + 1);
+#if 0 /* this is currently broken by UIDPLUS */
+ /* or skip by mailbox's recorded last */
+ else if (j > stream->uid_last)
+ sprintf (tmp,"Message %lu UID %lu greater than last %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,stream->uid_last);
+#endif
+ else { /* normal UID case */
+ prevuid = elt->private.uid = j;
+#if 1 /* temporary kludge for UIDPLUS */
+ if (prevuid > stream->uid_last) {
+ stream->uid_last = prevuid;
+ LOCAL->ddirty = LOCAL->dirty = T;
+ }
+#endif
+ break; /* exit this cruft */
+ }
+ mm_log (tmp,WARN);
+ /* invalidate UID validity */
+ stream->uid_validity = 0;
+ elt->private.uid = 0;
+ }
+ break;
+ }
+ }
+ /* otherwise fall into S case */
+
+ case 'S': /* possible Status: line */
+ if (s[0] == 'S' && s[1] == 't' && s[2] == 'a' && s[3] == 't' &&
+ s[4] == 'u' && s[5] == 's' && s[6] == ':') {
+ retain = NIL; /* don't retain continuation */
+ s += 6; /* advance to status flags */
+ do switch (*s++) {/* parse flags */
+ case 'R': /* message read */
+ elt->seen = T;
+ break;
+ case 'O': /* message old */
+ if (elt->recent) {
+ elt->recent = NIL;
+ recent--; /* it really wasn't recent */
+ }
+ break;
+ case 'D': /* message deleted */
+ elt->deleted = T;
+ break;
+ case 'F': /* message flagged */
+ elt->flagged = T;
+ break;
+ case 'A': /* message answered */
+ elt->answered = T;
+ break;
+ case 'T': /* message is a draft */
+ elt->draft = T;
+ break;
+ default: /* some other crap */
+ break;
+ } while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n')));
+ break; /* all done */
+ }
+ /* otherwise fall into default case */
+
+ default: /* ordinary header line */
+ if ((*s == 'S') || (*s == 's') ||
+ (((*s == 'X') || (*s == 'x')) && (s[1] == '-'))) {
+ unsigned char *e,*v;
+ /* must match what mail_filter() does */
+ for (u = s,v = tmp,e = u + min (i,MAILTMPLEN - 1);
+ (u < e) && ((c = (*u ? *u : (*u = ' '))) != ':') &&
+ ((c > ' ') || ((c != ' ') && (c != '\t') &&
+ (c != '\r') && (c != '\n')));
+ *v++ = *u++);
+ *v = '\0'; /* tie off */
+ /* matches internal header? */
+ if (!compare_cstring (tmp,"STATUS") ||
+ !compare_cstring (tmp,"X-STATUS") ||
+ !compare_cstring (tmp,"X-KEYWORDS") ||
+ !compare_cstring (tmp,"X-UID") ||
+ !compare_cstring (tmp,"X-IMAP") ||
+ !compare_cstring (tmp,"X-IMAPBASE")) {
+ char err[MAILTMPLEN];
+ sprintf (err,"Discarding bogus %s header in message %lu",
+ (char *) tmp,elt->msgno);
+ mm_log (err,WARN);
+ retain = NIL; /* don't retain continuation */
+ break; /* different case or something */
+ }
+ }
+ /* retain or non-continuation? */
+ if (retain || ((*s != ' ') && (*s != '\t'))) {
+ retain = T; /* retaining continuation now */
+ /* line length in CRLF format newline */
+ k = i + (((i < 2) || (s[i - 2] != '\r')) ? 1 : 0);
+ /* header size */
+ elt->rfc822_size = elt->private.spare.data += k;
+ }
+ else {
+ char err[MAILTMPLEN];
+ sprintf (err,"Discarding bogus continuation in msg %lu: %.80s",
+ elt->msgno,(char *) s);
+ if (u = strpbrk (err,"\r\n")) *u = '\0';
+ mm_log (err,WARN);
+ break; /* different case or something */
+ }
+ break;
+ }
+ } while (i && (*t != '\n') && ((*t != '\r') || (t[1] != '\n')));
+ /* "internal" header sans trailing newline */
+ if (i) elt->private.spare.data -= 2;
+ /* assign a UID if none found */
+ if (((nmsgs > 1) || !pseudoseen) && !elt->private.uid) {
+ prevuid = elt->private.uid = ++stream->uid_last;
+ elt->private.dirty = T;
+ LOCAL->ddirty = T; /* force update */
+ }
+ else elt->private.dirty = elt->recent;
+
+ /* note size of header, location of text */
+ elt->private.msg.header.text.size =
+ (elt->private.msg.text.offset =
+ (LOCAL->filesize + GETPOS (&bs)) - elt->private.special.offset) -
+ elt->private.special.text.size;
+ k = m = 0; /* no previous line size yet */
+ /* note current position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ if (i) do { /* look for next message */
+ s = unix_mbxline (stream,&bs,&i);
+ if (i) { /* got new data? */
+ VALID (s,t,ti,zn); /* yes, parse line */
+ if (!ti) { /* not a header line, add it to message */
+ if (s[i - 1] == '\n')
+ elt->rfc822_size +=
+ k = i + (m = (((i < 2) || s[i - 2] != '\r') ? 1 : 0));
+ else { /* file does not end with newline! */
+ elt->rfc822_size += i;
+ k = m = 0;
+ }
+ /* update current position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ }
+ }
+ } while (i && !ti); /* until found a header */
+ elt->private.msg.text.text.size = j -
+ (elt->private.special.offset + elt->private.msg.text.offset);
+ if (k == 2) { /* last line was blank? */
+ elt->private.msg.text.text.size -= (m ? 1 : 2);
+ elt->rfc822_size -= 2;
+ }
+ /* until end of buffer */
+ } while (!stream->sniff && i);
+ if (pseudoseen) { /* flush pseudo-message if present */
+ /* decrement recent count */
+ if (mail_elt (stream,1)->recent) recent--;
+ /* and the exists count */
+ mail_exists (stream,nmsgs--);
+ mail_expunged(stream,1);/* fake an expunge of that message */
+ }
+ /* need to start a new UID validity? */
+ if (!stream->uid_validity) {
+ stream->uid_validity = (unsigned long) time (0);
+ if (nmsgs) { /* don't bother if empty file */
+ /* make dirty to restart UID epoch */
+ LOCAL->ddirty = LOCAL->dirty = T;
+ /* need to rewrite msg 1 if not pseudo */
+ if (!LOCAL->pseudo) mail_elt (stream,1)->private.dirty = T;
+ mm_log ("Assigning new unique identifiers to all messages",NIL);
+ }
+ }
+ stream->nmsgs = oldnmsgs; /* whack it back down */
+ stream->silent = silent; /* restore old silent setting */
+ /* notify upper level of new mailbox sizes */
+ mail_exists (stream,nmsgs);
+ mail_recent (stream,recent);
+ /* mark dirty so O flags are set */
+ if (recent) LOCAL->dirty = T;
+ }
+ }
+ /* no change, don't babble if never got time */
+ else if (LOCAL->filetime && LOCAL->filetime != sbuf.st_mtime)
+ mm_log ("New mailbox modification time but apparently no changes",WARN);
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ LOCAL->filetime = sbuf.st_mtime;
+ return T; /* return the winnage */
+}
+
+/* UNIX read line from mailbox
+ * Accepts: mail stream
+ * stringstruct
+ * pointer to line size
+ * Returns: pointer to input line
+ */
+
+char *unix_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size)
+{
+ unsigned long i,j,k,m;
+ char *s,*t,*te;
+ char *ret = "";
+ /* flush old buffer */
+ if (LOCAL->line) fs_give ((void **) &LOCAL->line);
+ /* if buffer needs refreshing */
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ if (SIZE (bs)) { /* find newline */
+ /* end of fast scan */
+ te = (t = (s = bs->curpos) + bs->cursize) - 12;
+ while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
+ --s; /* back up */
+ break; /* exit loop */
+ }
+ /* final character-at-a-time scan */
+ while ((s < t) && (*s != '\n')) ++s;
+ /* difficult case if line spans buffer */
+ if ((i = s - bs->curpos) == bs->cursize) {
+ /* have space in line buffer? */
+ if (i > LOCAL->linebuflen) {
+ fs_give ((void **) &LOCAL->linebuf);
+ LOCAL->linebuf = (char *) fs_get (LOCAL->linebuflen = i);
+ }
+ /* remember what we have so far */
+ memcpy (LOCAL->linebuf,bs->curpos,i);
+ /* load next buffer */
+ SETPOS (bs,k = GETPOS (bs) + i);
+ /* end of fast scan */
+ te = (t = (s = bs->curpos) + bs->cursize) - 12;
+ /* fast scan in overlap buffer */
+ while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
+ --s; /* back up */
+ break; /* exit loop */
+ }
+
+ /* final character-at-a-time scan */
+ while ((s < t) && (*s != '\n')) ++s;
+ /* huge line? */
+ if ((j = s - bs->curpos) == bs->cursize) {
+ SETPOS (bs,GETPOS (bs) + j);
+ /* look for end of line (s-l-o-w!!) */
+ for (m = SIZE (bs); m && (SNX (bs) != '\n'); --m,++j);
+ SETPOS (bs,k); /* go back to where it started */
+ }
+ /* got size of data, make buffer for return */
+ ret = LOCAL->line = (char *) fs_get (i + j + 2);
+ /* copy first chunk */
+ memcpy (ret,LOCAL->linebuf,i);
+ while (j) { /* copy remainder */
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ memcpy (ret + i,bs->curpos,k = min (j,bs->cursize));
+ i += k; /* account for this much read in */
+ j -= k;
+ bs->curpos += k; /* increment new position */
+ bs->cursize -= k; /* eat that many bytes */
+ }
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ /* read newline at end */
+ if (SIZE (bs)) ret[i++] = SNX (bs);
+ ret[i] = '\0'; /* makes debugging easier */
+ }
+ else { /* this is easy */
+ ret = bs->curpos; /* string it at this position */
+ bs->curpos += ++i; /* increment new position */
+ bs->cursize -= i; /* eat that many bytes */
+ }
+ *size = i; /* return that to user */
+ }
+ else *size = 0; /* end of data, return empty */
+ return ret;
+}
+
+/* UNIX make pseudo-header
+ * Accepts: MAIL stream
+ * buffer to write pseudo-header
+ * Returns: length of pseudo-header
+ */
+
+unsigned long unix_pseudo (MAILSTREAM *stream,char *hdr)
+{
+ int i;
+ char *s,*t,tmp[MAILTMPLEN];
+ time_t now = time(0);
+ rfc822_fixed_date (tmp);
+ sprintf (hdr,"From %s %.24s\r\nDate: %s\r\nFrom: %s <%s@%.80s>\r\nSubject: %s\r\nMessage-ID: <%lu@%.80s>\r\nX-IMAP: %010ld %010ld",
+ pseudo_from,ctime (&now),
+ tmp,pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
+ (unsigned long) now,mylocalhost (),stream->uid_validity,
+ stream->uid_last);
+ for (t = hdr + strlen (hdr),i = 0; i < NUSERFLAGS; ++i)
+ if (stream->user_flags[i])
+ sprintf (t += strlen (t)," %s",stream->user_flags[i]);
+ strcpy (t += strlen (t),"\r\nStatus: RO\r\n\r\n");
+ for (s = pseudo_msg,t += strlen (t); *s; *t++ = *s++)
+ if (*s == '\n') *t++ = '\r';
+ *t++ = '\r'; *t++ = '\n'; *t++ = '\r'; *t++ = '\n';
+ *t = '\0'; /* tie off pseudo header */
+ return t - hdr; /* return length of pseudo header */
+}
+
+/* UNIX make status string
+ * Accepts: MAIL stream
+ * destination string to write
+ * message cache entry
+ * UID to write if non-zero (else use elt->private.uid)
+ * non-zero flag to write UID (.LT. 0 to write UID base info too)
+ * Returns: length of string
+ */
+
+unsigned long unix_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
+ unsigned long uid,long flag)
+{
+ char *t,stack[64];
+ char *s = status;
+ unsigned long n;
+ unsigned long pad = 50;
+ /* This used to use sprintf(), but thanks to certain cretinous C libraries
+ with horribly slow implementations of sprintf() I had to change it to this
+ mess. At least it should be fast. */
+ *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't'; *s++ = 'u'; *s++ = 's';
+ *s++ = ':'; *s++ = ' ';
+ if (elt->seen) *s++ = 'R';
+ /* only write O if have a UID */
+ if (flag && (!elt->recent || LOCAL->appending)) *s++ = 'O';
+ *s++ = '\r'; *s++ = '\n';
+ *s++ = 'X'; *s++ = '-'; *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't';
+ *s++ = 'u'; *s++ = 's'; *s++ = ':'; *s++ = ' ';
+ if (elt->deleted) *s++ = 'D';
+ if (elt->flagged) *s++ = 'F';
+ if (elt->answered) *s++ = 'A';
+ if (elt->draft) *s++ = 'T';
+ *s++ = '\r'; *s++ = '\n';
+
+ *s++ = 'X'; *s++ = '-'; *s++ = 'K'; *s++ = 'e'; *s++ = 'y'; *s++ = 'w';
+ *s++ = 'o'; *s++ = 'r'; *s++ = 'd'; *s++ = 's'; *s++ = ':';
+ if (n = elt->user_flags) do {
+ *s++ = ' ';
+ for (t = stream->user_flags[find_rightmost_bit (&n)]; *t; *s++ = *t++);
+ } while (n);
+ n = s - status; /* get size of stuff so far */
+ /* pad X-Keywords to make size constant */
+ if (n < pad) for (n = pad - n; n > 0; --n) *s++ = ' ';
+ *s++ = '\r'; *s++ = '\n';
+ if (flag) { /* want to include UID? */
+ t = stack;
+ /* push UID digits on the stack */
+ n = uid ? uid : elt->private.uid;
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ *s++ = 'X'; *s++ = '-'; *s++ = 'U'; *s++ = 'I'; *s++ = 'D'; *s++ = ':';
+ *s++ = ' ';
+ /* pop UID from stack */
+ while (t > stack) *s++ = *--t;
+ *s++ = '\r'; *s++ = '\n';
+ }
+ /* end of extended message status */
+ *s++ = '\r'; *s++ = '\n'; *s = '\0';
+ return s - status; /* return size of resulting string */
+}
+
+/* Rewrite mailbox file
+ * Accepts: MAIL stream, must be critical and locked
+ * return pointer to number of expunged messages if want expunge
+ * lock file name
+ * expunge sequence, not deleted flag
+ * Returns: T if success and mailbox unlocked, NIL if failure
+ */
+
+#define OVERFLOWBUFLEN 8192 /* initial overflow buffer length */
+
+long unix_rewrite (MAILSTREAM *stream,unsigned long *nexp,char *lock,
+ long flags)
+{
+ MESSAGECACHE *elt;
+ UNIXFILE f;
+ char *s;
+ struct utimbuf times;
+ long ret,flag;
+ unsigned long i,j;
+ unsigned long recent = stream->recent;
+ unsigned long size = LOCAL->pseudo ? unix_pseudo (stream,LOCAL->buf) : 0;
+ if (nexp) *nexp = 0; /* initially nothing expunged */
+ /* calculate size of mailbox after rewrite */
+ for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs; i++) {
+ elt = mail_elt (stream,i); /* get cache */
+ if (!(nexp && elt->deleted && (flags ? elt->sequence : T))) {
+ /* add RFC822 size of this message */
+ size += elt->private.special.text.size + elt->private.spare.data +
+ unix_xstatus (stream,LOCAL->buf,elt,NIL,flag) +
+ elt->private.msg.text.text.size + 2;
+ flag = 1; /* only count X-IMAPbase once */
+ }
+ }
+ if (!size) { /* no messages and no pseudo, make one now */
+ size = unix_pseudo (stream,LOCAL->buf);
+ LOCAL->pseudo = T;
+ }
+ /* extend the file as necessary */
+ if (ret = unix_extend (stream,size)) {
+ /* Set up buffered I/O file structure
+ * curpos current position being written through buffering
+ * filepos current position being written physically to the disk
+ * bufpos current position being written in the buffer
+ * protect current maximum position that can be written to the disk
+ * before buffering is forced
+ * The code tries to buffer so that that disk is written in multiples of
+ * OVERBLOWBUFLEN bytes.
+ */
+ f.stream = stream; /* note mail stream */
+ f.curpos = f.filepos = 0; /* start of file */
+ f.protect = stream->nmsgs ? /* initial protection pointer */
+ mail_elt (stream,1)->private.special.offset : 8192;
+ f.bufpos = f.buf = (char *) fs_get (f.buflen = OVERFLOWBUFLEN);
+
+ if (LOCAL->pseudo) /* update pseudo-header */
+ unix_write (&f,LOCAL->buf,unix_pseudo (stream,LOCAL->buf));
+ /* loop through all messages */
+ for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs;) {
+ elt = mail_elt (stream,i);/* get cache */
+ /* expunge this message? */
+ if (nexp && elt->deleted && (flags ? elt->sequence : T)) {
+ /* one less recent message */
+ if (elt->recent) --recent;
+ mail_expunged(stream,i);/* notify upper levels */
+ ++*nexp; /* count up one more expunged message */
+ }
+ else { /* preserve this message */
+ i++; /* advance to next message */
+ if ((flag < 0) || /* need to rewrite message? */
+ elt->private.dirty ||
+ (((unsigned long) f.curpos) != elt->private.special.offset) ||
+ (elt->private.msg.header.text.size !=
+ (elt->private.spare.data +
+ unix_xstatus (stream,LOCAL->buf,elt,NIL,flag)))) {
+ unsigned long newoffset = f.curpos;
+ /* yes, seek to internal header */
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
+ /* protection pointer moves to RFC822 header */
+ f.protect = elt->private.special.offset +
+ elt->private.msg.header.offset;
+ /* write internal header */
+ unix_write (&f,LOCAL->buf,elt->private.special.text.size);
+ /* get RFC822 header */
+ s = unix_header (stream,elt->msgno,&j,NIL);
+ /* in case this got decremented */
+ elt->private.msg.header.offset = elt->private.special.text.size;
+ /* header size, sans trailing newline */
+ if ((j < 4) || (s[j - 4] == '\r')) j -= 2;
+ if (j != elt->private.spare.data) fatal ("header size inconsistent");
+ /* protection pointer moves to RFC822 text */
+ f.protect = elt->private.special.offset +
+ elt->private.msg.text.offset;
+ unix_write (&f,s,j); /* write RFC822 header */
+ /* write status and UID */
+ unix_write (&f,LOCAL->buf,
+ j = unix_xstatus (stream,LOCAL->buf,elt,NIL,flag));
+ flag = 1; /* only write X-IMAPbase once */
+ /* new file header size */
+ elt->private.msg.header.text.size = elt->private.spare.data + j;
+
+ /* did text move? */
+ if (f.curpos != f.protect) {
+ /* get message text */
+ s = unix_text_work (stream,elt,&j,FT_INTERNAL);
+ /* can't happen it says here */
+ if (j > elt->private.msg.text.text.size)
+ fatal ("text size inconsistent");
+ /* new text offset, status/UID may change it */
+ elt->private.msg.text.offset = f.curpos - newoffset;
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset : (f.curpos + j + 2);
+ unix_write (&f,s,j);/* write text */
+ /* write trailing newline */
+ unix_write (&f,"\r\n",2);
+ }
+ else { /* tie off header and status */
+ unix_write (&f,NIL,NIL);
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset : size;
+ /* locate end of message text */
+ j = f.filepos + elt->private.msg.text.text.size;
+ /* trailing newline already there? */
+ if (f.protect == (off_t) (j + 2)) f.curpos = f.filepos = f.protect;
+ else { /* trailing newline missing, write it */
+ f.curpos = f.filepos = j;
+ unix_write (&f,"\r\n",2);
+ }
+ }
+ /* new internal header offset */
+ elt->private.special.offset = newoffset;
+ elt->private.dirty =NIL;/* message is now clean */
+ }
+ else { /* no need to rewrite this message */
+ /* tie off previous message if needed */
+ unix_write (&f,NIL,NIL);
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset : size;
+ /* locate end of message text */
+ j = f.filepos + elt->private.special.text.size +
+ elt->private.msg.header.text.size +
+ elt->private.msg.text.text.size;
+ /* trailing newline already there? */
+ if (f.protect == (off_t) (j + 2)) f.curpos = f.filepos = f.protect;
+ else { /* trailing newline missing, write it */
+ f.curpos = f.filepos = j;
+ unix_write (&f,"\r\n",2);
+ }
+ }
+ }
+ }
+
+ unix_write (&f,NIL,NIL); /* tie off final message */
+ if (size != ((unsigned long) f.filepos)) fatal ("file size inconsistent");
+ fs_give ((void **) &f.buf); /* free buffer */
+ /* make sure tied off */
+ ftruncate (LOCAL->fd,LOCAL->filesize = size);
+ fsync (LOCAL->fd); /* make sure the updates take */
+ if (size && (flag < 0)) fatal ("lost UID base information");
+ /* no longer dirty */
+ LOCAL->ddirty = LOCAL->dirty = NIL;
+ /* notify upper level of new mailbox sizes */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ /* set atime to now, mtime a second earlier */
+ times.modtime = (times.actime = time (0)) -1;
+ /* set the times, note change */
+ if (!utime (stream->mailbox,&times)) LOCAL->filetime = times.modtime;
+ /* flush the lock file */
+ unix_unlock (LOCAL->fd,stream,lock);
+ }
+ return ret; /* return state from algorithm */
+}
+
+/* Extend UNIX mailbox file
+ * Accepts: MAIL stream
+ * new desired size
+ * Return: T if success, else NIL
+ */
+
+long unix_extend (MAILSTREAM *stream,unsigned long size)
+{
+ unsigned long i = (size > ((unsigned long) LOCAL->filesize)) ?
+ size - ((unsigned long) LOCAL->filesize) : 0;
+ if (i) { /* does the mailbox need to grow? */
+ if (i > LOCAL->buflen) { /* make sure have enough space */
+ /* this user won the lottery all right */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1);
+ }
+ memset (LOCAL->buf,'\0',i); /* get a block of nulls */
+ while (T) { /* until write successful or punt */
+ lseek (LOCAL->fd,LOCAL->filesize,L_SET);
+ if ((write (LOCAL->fd,LOCAL->buf,i) >= 0) && !fsync (LOCAL->fd)) break;
+ else {
+ long e = errno; /* note error before doing ftruncate */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ if (mm_diskerror (stream,e,NIL)) {
+ fsync (LOCAL->fd); /* user chose to punt */
+ sprintf (LOCAL->buf,"Unable to extend mailbox: %s",strerror (e));
+ if (!stream->silent) mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ }
+ }
+ }
+ return LONGT;
+}
+
+/* Write data to buffered file
+ * Accepts: buffered file pointer
+ * file data or NIL to indicate "flush buffer"
+ * date size (ignored for "flush buffer")
+ * Does not return until success
+ */
+
+void unix_write (UNIXFILE *f,char *buf,unsigned long size)
+{
+ unsigned long i,j,k;
+ if (buf) { /* doing buffered write? */
+ i = f->bufpos - f->buf; /* yes, get size of current buffer data */
+ /* yes, have space in current buffer chunk? */
+ if (j = i ? ((f->buflen - i) % OVERFLOWBUFLEN) : f->buflen) {
+ /* yes, fill up buffer as much as we can */
+ memcpy (f->bufpos,buf,k = min (j,size));
+ f->bufpos += k; /* new buffer position */
+ f->curpos += k; /* new current position */
+ if (j -= k) return; /* all done if still have buffer free space */
+ buf += k; /* full, get new unwritten data pointer */
+ size -= k; /* new data size */
+ i += k; /* new buffer data size */
+ }
+ /* This chunk of the buffer is full. See if can make some space by
+ * writing to the disk, if there's enough unprotected space to do so.
+ * Try to fill out any unaligned chunk, along with any subsequent full
+ * chunks that will fit in unprotected space.
+ */
+ /* any unprotected space we can write to? */
+ if (j = min (i,f->protect - f->filepos)) {
+ /* yes, filepos not at chunk boundary? */
+ if ((k = f->filepos % OVERFLOWBUFLEN) && ((k = OVERFLOWBUFLEN - k) < j))
+ j -= k; /* yes, and can write out partial chunk */
+ else k = 0; /* no partial chunk to write */
+ /* if at least a chunk free, write that too */
+ if (j > OVERFLOWBUFLEN) k += j - (j % OVERFLOWBUFLEN);
+ if (k) { /* write data if there is anything we can */
+ unix_phys_write (f,f->buf,k);
+ /* slide buffer */
+ if (i -= k) memmove (f->buf,f->buf + k,i);
+ f->bufpos = f->buf + i; /* new end of buffer */
+ }
+ }
+
+ /* Have flushed the buffer as best as possible. All done if no more
+ * data to write. Otherwise, if the buffer is empty AND if the unwritten
+ * data is larger than a chunk AND the unprotected space is also larger
+ * than a chunk, then write as many chunks as we can directly from the
+ * data. Buffer the rest, expanding the buffer as needed.
+ */
+ if (size) { /* have more data that we need to buffer? */
+ /* can write any of it to disk instead? */
+ if ((f->bufpos == f->buf) &&
+ ((j = min (f->protect - f->filepos,size)) > OVERFLOWBUFLEN)) {
+ /* write as much as we can right now */
+ unix_phys_write (f,buf,j -= (j % OVERFLOWBUFLEN));
+ buf += j; /* new data pointer */
+ size -= j; /* new data size */
+ f->curpos += j; /* advance current pointer */
+ }
+ if (size) { /* still have data that we need to buffer? */
+ /* yes, need to expand the buffer? */
+ if ((i = ((f->bufpos + size) - f->buf)) > f->buflen) {
+ /* note current position in buffer */
+ j = f->bufpos - f->buf;
+ i += OVERFLOWBUFLEN; /* yes, grow another chunk */
+ fs_resize ((void **) &f->buf,f->buflen = i - (i % OVERFLOWBUFLEN));
+ /* in case buffer relocated */
+ f->bufpos = f->buf + j;
+ }
+ /* buffer remaining data */
+ memcpy (f->bufpos,buf,size);
+ f->bufpos += size; /* new end of buffer */
+ f->curpos += size; /* advance current pointer */
+ }
+ }
+ }
+ else { /* flush buffer to disk */
+ unix_phys_write (f,f->buf,i = f->bufpos - f->buf);
+ f->bufpos = f->buf; /* reset buffer */
+ /* update positions */
+ f->curpos = f->protect = f->filepos;
+ }
+}
+
+/* Physical disk write
+ * Accepts: buffered file pointer
+ * buffer address
+ * buffer size
+ * Does not return until success
+ */
+
+void unix_phys_write (UNIXFILE *f,char *buf,size_t size)
+{
+ MAILSTREAM *stream = f->stream;
+ /* write data at desired position */
+ while (size && ((lseek (LOCAL->fd,f->filepos,L_SET) < 0) ||
+ (write (LOCAL->fd,buf,size) < 0))) {
+ int e;
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Unable to write to mailbox: %s",strerror (e = errno));
+ mm_log (tmp,ERROR);
+ mm_diskerror (NIL,e,T); /* serious problem, must retry */
+ }
+ f->filepos += size; /* update file position */
+}
diff --git a/imap/src/osdep/nt/unixnt.h b/imap/src/osdep/nt/unixnt.h
new file mode 100644
index 00000000..1305184f
--- /dev/null
+++ b/imap/src/osdep/nt/unixnt.h
@@ -0,0 +1,161 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 20 December 1989
+ * Last Edited: 30 August 2006
+ */
+
+
+/* DEDICATION
+ *
+ * This file is dedicated to my dog, Unix, also known as Yun-chan and
+ * Unix J. Terwilliker Jehosophat Aloysius Monstrosity Animal Beast. Unix
+ * passed away at the age of 11 1/2 on September 14, 1996, 12:18 PM PDT, after
+ * a two-month bout with cirrhosis of the liver.
+ *
+ * He was a dear friend, and I miss him terribly.
+ *
+ * Lift a leg, Yunie. Luv ya forever!!!!
+ */
+
+/* Validate line
+ * Accepts: pointer to candidate string to validate as a From header
+ * return pointer to end of date/time field
+ * return pointer to offset from t of time (hours of ``mmm dd hh:mm'')
+ * return pointer to offset from t of time zone (if non-zero)
+ * Returns: t,ti,zn set if valid From string, else ti is NIL
+ */
+
+#define VALID(s,x,ti,zn) { \
+ ti = 0; \
+ if ((*s == 'F') && (s[1] == 'r') && (s[2] == 'o') && (s[3] == 'm') && \
+ (s[4] == ' ')) { \
+ for (x = s + 5; *x && *x != '\012'; x++); \
+ if (*x) { \
+ if (x[-1] == '\015') --x; \
+ if (x - s >= 41) { \
+ for (zn = -1; x[zn] != ' '; zn--); \
+ if ((x[zn-1] == 'm') && (x[zn-2] == 'o') && (x[zn-3] == 'r') && \
+ (x[zn-4] == 'f') && (x[zn-5] == ' ') && (x[zn-6] == 'e') && \
+ (x[zn-7] == 't') && (x[zn-8] == 'o') && (x[zn-9] == 'm') && \
+ (x[zn-10] == 'e') && (x[zn-11] == 'r') && (x[zn-12] == ' '))\
+ x += zn - 12; \
+ } \
+ if (x - s >= 27) { \
+ if (x[-5] == ' ') { \
+ if (x[-8] == ':') zn = 0,ti = -5; \
+ else if (x[-9] == ' ') ti = zn = -9; \
+ else if ((x[-11] == ' ') && ((x[-10]=='+') || (x[-10]=='-'))) \
+ ti = zn = -11; \
+ } \
+ else if (x[-4] == ' ') { \
+ if (x[-9] == ' ') zn = -4,ti = -9; \
+ } \
+ else if (x[-6] == ' ') { \
+ if ((x[-11] == ' ') && ((x[-5] == '+') || (x[-5] == '-'))) \
+ zn = -6,ti = -11; \
+ } \
+ if (ti && !((x[ti - 3] == ':') && \
+ (x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') && \
+ (x[ti - 3] == ' ') && (x[ti - 7] == ' ') && \
+ (x[ti - 11] == ' '))) ti = 0; \
+ } \
+ } \
+ } \
+}
+
+/* You are not expected to understand this macro, but read the next page if
+ * you are not faint of heart.
+ *
+ * Known formats to the VALID macro are:
+ * From user Wed Dec 2 05:53 1992
+ * BSD From user Wed Dec 2 05:53:22 1992
+ * SysV From user Wed Dec 2 05:53 PST 1992
+ * rn From user Wed Dec 2 05:53:22 PST 1992
+ * From user Wed Dec 2 05:53 -0700 1992
+ * emacs From user Wed Dec 2 05:53:22 -0700 1992
+ * From user Wed Dec 2 05:53 1992 PST
+ * From user Wed Dec 2 05:53:22 1992 PST
+ * From user Wed Dec 2 05:53 1992 -0700
+ * Solaris From user Wed Dec 2 05:53:22 1992 -0700
+ *
+ * Plus all of the above with `` remote from xxx'' after it. Thank you very
+ * much, smail and Solaris, for making my life considerably more complicated.
+ */
+
+/*
+ * What? You want to understand the VALID macro anyway? Alright, since you
+ * insist. Actually, it isn't really all that difficult, provided that you
+ * take it step by step.
+ *
+ * Line 1 Initializes the return ti value to failure (0);
+ * Lines 2-3 Validates that the 1st-5th characters are ``From ''.
+ * Lines 4-6 Validates that there is an end of line and points x at it.
+ * Lines 7-14 First checks to see if the line is at least 41 characters long.
+ * If so, it scans backwards to find the rightmost space. From
+ * that point, it scans backwards to see if the string matches
+ * `` remote from''. If so, it sets x to point to the space at
+ * the start of the string.
+ * Line 15 Makes sure that there are at least 27 characters in the line.
+ * Lines 16-21 Checks if the date/time ends with the year (there is a space
+ * five characters back). If there is a colon three characters
+ * further back, there is no timezone field, so zn is set to 0
+ * and ti is set in front of the year. Otherwise, there must
+ * either to be a space four characters back for a three-letter
+ * timezone, or a space six characters back followed by a + or -
+ * for a numeric timezone; in either case, zn and ti become the
+ * offset of the space immediately before it.
+ * Lines 22-24 Are the failure case for line 14. If there is a space four
+ * characters back, it is a three-letter timezone; there must be a
+ * space for the year nine characters back. zn is the zone
+ * offset; ti is the offset of the space.
+ * Lines 25-28 Are the failure case for line 20. If there is a space six
+ * characters back, it is a numeric timezone; there must be a
+ * space eleven characters back and a + or - five characters back.
+ * zn is the zone offset; ti is the offset of the space.
+ * Line 29-32 If ti is valid, make sure that the string before ti is of the
+ * form www mmm dd hh:mm or www mmm dd hh:mm:ss, otherwise
+ * invalidate ti. There must be a colon three characters back
+ * and a space six or nine characters back (depending upon
+ * whether or not the character six characters back is a colon).
+ * There must be a space three characters further back (in front
+ * of the day), one seven characters back (in front of the month),
+ * and one eleven characters back (in front of the day of week).
+ * ti is set to be the offset of the space before the time.
+ *
+ * Why a macro? It gets invoked a *lot* in a tight loop. On some of the
+ * newer pipelined machines it is faster being open-coded than it would be if
+ * subroutines are called.
+ *
+ * Why does it scan backwards from the end of the line, instead of doing the
+ * much easier forward scan? There is no deterministic way to parse the
+ * ``user'' field, because it may contain unquoted spaces! Yes, I tested it to
+ * see if unquoted spaces were possible. They are, and I've encountered enough
+ * evil mail to be totally unwilling to trust that ``it will never happen''.
+ */
+
+/* Build parameters */
+
+#define KODRETRY 15 /* kiss-of-death retry in seconds */
+#define LOCKTIMEOUT 5 /* lock timeout in minutes */
diff --git a/imap/src/osdep/nt/write.c b/imap/src/osdep/nt/write.c
new file mode 100644
index 00000000..c7854815
--- /dev/null
+++ b/imap/src/osdep/nt/write.c
@@ -0,0 +1,59 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Write data, treating partial writes as an error
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 May 1995
+ * Last Edited: 30 August 2006
+ */
+
+/* The whole purpose of this unfortunate routine is to deal with DOS and
+ * certain cretinous versions of UNIX which decided that the "bytes actually
+ * written" return value from write() gave them license to use that for things
+ * that are really errors, such as disk quota exceeded, maximum file size
+ * exceeded, disk full, etc.
+ *
+ * BSD won't screw us this way on the local filesystem, but who knows what
+ * some NFS-mounted filesystem will do.
+ */
+
+#undef write
+
+/* Write data to file
+ * Accepts: file descriptor
+ * I/O vector structure
+ * number of vectors in structure
+ * Returns: number of bytes written if successful, -1 if failure
+ */
+
+long maxposint = (long)((((unsigned long) 1) << ((sizeof(int) * 8) - 1)) - 1);
+
+long safe_write (int fd,char *buf,long nbytes)
+{
+ long i,j;
+ if (nbytes > 0) for (i = nbytes; i; i -= j,buf += j) {
+ while (((j = write (fd,buf,(int) min (maxposint,i))) < 0) &&
+ (errno == EINTR));
+ if (j < 0) return j;
+ }
+ return nbytes;
+}
diff --git a/imap/src/osdep/nt/yunchan.c b/imap/src/osdep/nt/yunchan.c
new file mode 100644
index 00000000..8c1f6905
--- /dev/null
+++ b/imap/src/osdep/nt/yunchan.c
@@ -0,0 +1,286 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Unix compatibility routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 14 September 1996
+ * Last Edited: 30 August 2006
+ */
+
+
+/* DEDICATION
+ *
+ * This file is dedicated to my dog, Unix, also known as Yun-chan and
+ * Unix J. Terwilliker Jehosophat Aloysius Monstrosity Animal Beast. Unix
+ * passed away at the age of 11 1/2 on September 14, 1996, 12:18 PM PDT, after
+ * a two-month bout with cirrhosis of the liver.
+ *
+ * He was a dear friend, and I miss him terribly.
+ *
+ * Lift a leg, Yunie. Luv ya forever!!!!
+ */
+
+/* Emulator for BSD flock() call
+ * Accepts: file descriptor
+ * operation bitmask
+ * Returns: 0 if successful, -1 if failure
+ */
+
+/* Our friends in Redmond have decided that you can not write to any segment
+ * which has a shared lock. This screws up the shared-write mailbox drivers
+ * (mbx, mtx, and tenex). As a workaround, we'll only lock the first byte of
+ * the file, meaning that you can't write that byte shared.
+ * This behavior seems to be new as of NT 4.0.
+ */
+
+int flock (int fd,int op)
+{
+ HANDLE hdl = (HANDLE) _get_osfhandle (fd);
+ DWORD flags = (op & LOCK_NB) ? LOCKFILE_FAIL_IMMEDIATELY : 0;
+ OVERLAPPED offset = {NIL,NIL,0,0,NIL};
+ int ret = -1;
+ blocknotify_t bn = (blocknotify_t)
+ ((op & LOCK_NB) ? NIL : mail_parameters (NIL,GET_BLOCKNOTIFY,NIL));
+ if (hdl < 0) errno = EBADF; /* error in file descriptor */
+ else switch (op & ~LOCK_NB) { /* translate to LockFileEx() op */
+ case LOCK_EX: /* exclusive */
+ flags |= LOCKFILE_EXCLUSIVE_LOCK;
+ case LOCK_SH: /* shared */
+ if (!check_nt ()) return 0; /* always succeeds if not NT */
+ if (bn) (*bn) (BLOCK_FILELOCK,NIL);
+ /* bug for bug compatible with Unix */
+ UnlockFileEx (hdl,NIL,1,0,&offset);
+ /* lock the file as requested */
+ if (LockFileEx (hdl,flags,NIL,1,0,&offset)) ret = 0;
+ if (bn) (*bn) (BLOCK_NONE,NIL);
+ /* if failed */
+ if (ret) errno = (op & LOCK_NB) ? EAGAIN : EBADF;
+ break;
+ case LOCK_UN: /* unlock */
+ if (check_nt ()) UnlockFileEx (hdl,NIL,1,0,&offset);
+ ret = 0; /* always succeeds */
+ default: /* default */
+ errno = EINVAL; /* bad call */
+ break;
+ }
+ return ret;
+}
+
+/* Local storage */
+
+static char *loghdr; /* log file header string */
+static HANDLE loghdl = NIL; /* handle of event source */
+
+/* Emulator for BSD syslog() routine
+ * Accepts: priority
+ * message
+ * parameters
+ */
+
+void syslog (int priority,const char *message,...)
+{
+ va_list args;
+ LPTSTR strs[2];
+ char tmp[MAILTMPLEN]; /* callers must be careful not to pop this */
+ unsigned short etype;
+ if (!check_nt ()) return; /* no-op on non-NT system */
+ /* default event source */
+ if (!loghdl) openlog ("c-client",LOG_PID,LOG_MAIL);
+ switch (priority) { /* translate UNIX type into NT type */
+ case LOG_ALERT:
+ etype = EVENTLOG_ERROR_TYPE;
+ break;
+ case LOG_INFO:
+ etype = EVENTLOG_INFORMATION_TYPE;
+ break;
+ default:
+ etype = EVENTLOG_WARNING_TYPE;
+ }
+ va_start (args,message); /* initialize vararg mechanism */
+ vsprintf (tmp,message,args); /* build message */
+ strs[0] = loghdr; /* write header */
+ strs[1] = tmp; /* then the message */
+ /* report the event */
+ ReportEvent (loghdl,etype,(unsigned short) priority,2000,NIL,2,0,strs,NIL);
+ va_end (args);
+}
+
+
+/* Emulator for BSD openlog() routine
+ * Accepts: identity
+ * options
+ * facility
+ */
+
+void openlog (const char *ident,int logopt,int facility)
+{
+ char tmp[MAILTMPLEN];
+ if (!check_nt ()) return; /* no-op on non-NT system */
+ if (loghdl) fatal ("Duplicate openlog()!");
+ loghdl = RegisterEventSource (NIL,ident);
+ sprintf (tmp,(logopt & LOG_PID) ? "%s[%d]" : "%s",ident,getpid ());
+ loghdr = cpystr (tmp); /* save header for later */
+}
+
+/* Copy Unix string with CRLF newlines
+ * Accepts: destination string
+ * pointer to size of destination string buffer
+ * source string
+ * length of source string
+ * Returns: length of copied string
+ */
+
+unsigned long unix_crlfcpy (char **dst,unsigned long *dstl,char *src,
+ unsigned long srcl)
+{
+ unsigned long i,j;
+ char *d = src;
+ /* count number of LF's in source string(s) */
+ for (i = srcl,j = 0; j < srcl; j++) if (*d++ == '\012') i++;
+ /* flush destination buffer if too small */
+ if (*dst && (i > *dstl)) fs_give ((void **) dst);
+ if (!*dst) { /* make a new buffer if needed */
+ *dst = (char *) fs_get ((*dstl = i) + 1);
+ if (dstl) *dstl = i; /* return new buffer length to main program */
+ }
+ d = *dst; /* destination string */
+ /* copy strings, inserting CR's before LF's */
+ while (srcl--) switch (*src) {
+ case '\015': /* unlikely carriage return */
+ *d++ = *src++; /* copy it and any succeeding linefeed */
+ if (srcl && *src == '\012') {
+ *d++ = *src++;
+ srcl--;
+ }
+ break;
+ case '\012': /* line feed? */
+ *d++ ='\015'; /* yes, prepend a CR, drop into default case */
+ default: /* ordinary chararacter */
+ *d++ = *src++; /* just copy character */
+ break;
+ }
+ *d = '\0'; /* tie off destination */
+ return d - *dst; /* return length */
+}
+
+/* Length of Unix string after unix_crlfcpy applied
+ * Accepts: source string
+ * Returns: length of string
+ */
+
+unsigned long unix_crlflen (STRING *s)
+{
+ unsigned long pos = GETPOS (s);
+ unsigned long i = SIZE (s);
+ unsigned long j = i;
+ while (j--) switch (SNX (s)) {/* search for newlines */
+ case '\015': /* unlikely carriage return */
+ if (j && (CHR (s) == '\012')) {
+ SNX (s); /* eat the line feed */
+ j--;
+ }
+ break;
+ case '\012': /* line feed? */
+ i++;
+ default: /* ordinary chararacter */
+ break;
+ }
+ SETPOS (s,pos); /* restore old position */
+ return i;
+}
+
+/* Undoubtably, I'm going to regret these two routines in the future. I
+ * regret them now. Their purpose is to work around two problems in the
+ * VC++ 6.0 C library:
+ * (1) tmpfile() creates the file in the current directory instead of a
+ * temporary directory
+ * (2) tmpfile() and fclose() think that on NT systems, it works to unlink
+ * the file while it's still open, so there's no need for the _tmpfname
+ * hook at fclose(). Unfortunately, that doesn't work in Win2K.
+ * I would be delighted to have a better alternative.
+ */
+
+#undef fclose /* use the real fclose() in close_file() */
+
+/* Substitute for Microsoft's tmpfile() that uses the real temporary directory
+ * Returns: FILE structure if success, NIL if failure
+ */
+
+FILE *create_tempfile (void)
+{
+ FILE *ret = NIL;
+ char *s = _tempnam (getenv ("TEMP"),"msg");
+ if (s) { /* if got temporary name... */
+ /* open file, and stash name on _tmpfname */
+ if (ret = fopen (s,"w+b")) ret->_tmpfname = s;
+ else fs_give ((void **) &s);/* flush temporary string */
+ }
+ return ret;
+}
+
+
+/* Substitute for Microsoft's fclose() that always flushes _tmpfname
+ * Returns: FILE structure if success, NIL if failure
+ */
+
+int close_file (FILE *stream)
+{
+ int ret;
+ char *s = stream->_tmpfname;
+ stream->_tmpfname = NIL; /* just in case fclose() tries to delete it */
+ ret = fclose (stream); /* close the file */
+ if (s) { /* was there a _tmpfname? */
+ unlink (s); /* yup, delete it */
+ fs_give ((void **) &s); /* and flush the name */
+ }
+ return ret;
+}
+
+/* Get password from console
+ * Accepts: prompt
+ * Returns: password
+ */
+
+#define PWDLEN 128 /* used by Linux */
+
+char *getpass (const char *prompt)
+{
+ static char pwd[PWDLEN];
+ int ch,i,done;
+ fputs (prompt,stderr); /* output prompt */
+ for (i = done = 0; !done; ) switch (ch = _getch()) {
+ case 0x03: /* CTRL/C stops program */
+ _exit (1);
+ case '\b': /* BACKSPACE erase previous character */
+ if (i) pwd[--i] = '\0';
+ break;
+ case '\n': case '\r': /* CR or LF terminates string */
+ done = 1;
+ break;
+ default: /* any other character is a pwd char */
+ if (i < (PWDLEN - 1)) pwd[i++] = ch;
+ break;
+ }
+ pwd[i] = '\0'; /* tie off string with null */
+ putchar ('\n'); /* echo newline */
+ return pwd;
+}
diff --git a/imap/src/osdep/nt/yunchan.h b/imap/src/osdep/nt/yunchan.h
new file mode 100644
index 00000000..25d60568
--- /dev/null
+++ b/imap/src/osdep/nt/yunchan.h
@@ -0,0 +1,86 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Unix compatibility routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 14 September 1996
+ * Last Edited: 30 August 2006
+ */
+
+
+/* DEDICATION
+ *
+ * This file is dedicated to my dog, Unix, also known as Yun-chan and
+ * Unix J. Terwilliker Jehosophat Aloysius Monstrosity Animal Beast. Unix
+ * passed away at the age of 11 1/2 on September 14, 1996, 12:18 PM PDT, after
+ * a two-month bout with cirrhosis of the liver.
+ *
+ * He was a dear friend, and I miss him terribly.
+ *
+ * Lift a leg, Yunie. Luv ya forever!!!!
+ */
+
+/* For flock() emulation */
+
+#define LOCK_SH 1
+#define LOCK_EX 2
+#define LOCK_NB 4
+#define LOCK_UN 8
+
+
+/* syslog() emulation */
+
+#define LOG_MAIL (2<<3) /* mail system */
+#define LOG_DAEMON (3<<3) /* system daemons */
+#define LOG_AUTH (4<<3) /* security/authorization messages */
+#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 signification condition */
+#define LOG_INFO 6 /* informational */
+#define LOG_DEBUG 7 /* debug-level messages */
+#define LOG_PID 0x01 /* log the pid with each message */
+#define LOG_CONS 0x02 /* log on the console if errors in sending */
+#define LOG_ODELAY 0x04 /* delay open until syslog() is called */
+#define LOG_NDELAY 0x08 /* don't delay open */
+#define LOG_NOWAIT 0x10 /* if forking to log on console, don't wait() */
+
+#define tmpfile create_tempfile
+#define fclose close_file
+#define fsync _commit
+#define ftruncate chsize
+#define gethostid clock
+#define sleep(x) Sleep (1000 * x)
+
+
+long alarm (long seconds);
+int flock (int fd,int op);
+void openlog (const char *ident,int logopt,int facility);
+void syslog (int priority,const char *message,...);
+unsigned long unix_crlfcpy (char **dst,unsigned long *dstl,char *src,
+ unsigned long srcl);
+unsigned long unix_crlflen (STRING *s);
+FILE *create_tempfile (void);
+int close_file (FILE *stream);
+char *getpass (const char *prompt);
diff --git a/imap/src/osdep/os2/auths.cmd b/imap/src/osdep/os2/auths.cmd
new file mode 100644
index 00000000..639f0807
--- /dev/null
+++ b/imap/src/osdep/os2/auths.cmd
@@ -0,0 +1,48 @@
+/* rexx */
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+/*
+ * Program: Authenticator Linkage Generator for OS/2
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 June 1999
+ * Last Edited: 30 August 2006
+ */
+'@echo off'
+/* Erase old authenticators list */
+'if exist auths.c del auths.c'
+parse arg args
+n=words(args)
+a_file='auths.c'
+c_file='linkage.c'
+h_file='linkage.h'
+call stream a_file, 'C', 'open write'
+call stream c_file, 'C', 'open write'
+call stream h_file, 'C', 'open write'
+do i=1 to n
+ arg=word(args,i)
+ call lineout a_file, '#include "auth_'arg'.c"'
+ call lineout h_file, 'extern AUTHENTICATOR auth_'arg';'
+ call lineout c_file, ' auth_link (&auth_'arg'); /* link in the 'arg' authenticator */'
+ end
+call stream h_file, 'C', 'close'
+call stream c_file, 'C', 'close'
+call stream a_file, 'C', 'close'
+exit 0
diff --git a/imap/src/osdep/os2/drivers.cmd b/imap/src/osdep/os2/drivers.cmd
new file mode 100644
index 00000000..8508834b
--- /dev/null
+++ b/imap/src/osdep/os2/drivers.cmd
@@ -0,0 +1,45 @@
+/* rexx */
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+/*
+ * Program: Authenticator Linkage Generator for OS/2
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 June 1999
+ * Last Edited: 30 August 2006
+ */
+'@echo off'
+/* Erase old authenticators list */
+'if exist linkage.h del linkage.h'
+'if exist linkage.c del linkage.c'
+parse arg args
+n=words(args)
+c_file='linkage.c'
+h_file='linkage.h'
+call stream c_file, 'C', 'open write'
+call stream h_file, 'C', 'open write'
+do i=1 to n
+ arg=word(args,i)
+ call lineout h_file, 'extern DRIVER 'arg'driver;'
+ call lineout c_file, ' mail_link (&'arg'driver); /* link in the 'arg' driver */'
+ end
+call stream h_file, 'C', 'close'
+call stream c_file, 'C', 'close'
+exit 0
diff --git a/imap/src/osdep/os2/dummy.h b/imap/src/osdep/os2/dummy.h
new file mode 100644
index 00000000..32650e06
--- /dev/null
+++ b/imap/src/osdep/os2/dummy.h
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 9 May 1991
+ * Last Edited: 30 August 2006
+ */
+
+/* Exported function prototypes */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat);
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long scan_contents (DRIVER *dtb,char *name,char *contents,
+ unsigned long csiz,unsigned long fsiz);
+long dummy_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz);
+long dummy_create (MAILSTREAM *stream,char *mailbox);
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode);
+long dummy_delete (MAILSTREAM *stream,char *mailbox);
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname);
+char *dummy_file (char *dst,char *name);
+long dummy_canonicalize (char *tmp,char *ref,char *pat);
diff --git a/imap/src/osdep/os2/dummyos2.c b/imap/src/osdep/os2/dummyos2.c
new file mode 100644
index 00000000..f54999fc
--- /dev/null
+++ b/imap/src/osdep/os2/dummyos2.c
@@ -0,0 +1,714 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines for OS2
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 24 May 1993
+ * Last Edited: 30 August 2006
+ */
+
+/* Thanks to Nicholas Sheppard for the original version */
+
+
+#include <ctype.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <pwd.h>
+#include <sys/stat.h>
+#include <io.h>
+#include <fcntl.h>
+#include <os2.h>
+#undef ADDRESS
+#include "mail.h"
+#include "osdep.h"
+#include "misc.h"
+#include "dummy.h"
+
+/* Function prototypes */
+
+DRIVER *dummy_valid (char *name);
+void *dummy_parameters (long function,void *value);
+void dummy_list_work (MAILSTREAM *stream,char *dir,char *pat,char *contents,
+ long level);
+long dummy_listed (MAILSTREAM *stream,char delimiter,char *name,
+ long attributes,char *contents);
+long dummy_subscribe (MAILSTREAM *stream,char *mailbox);
+MAILSTREAM *dummy_open (MAILSTREAM *stream);
+void dummy_close (MAILSTREAM *stream,long options);
+long dummy_ping (MAILSTREAM *stream);
+void dummy_check (MAILSTREAM *stream);
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options);
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+/* Dummy routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER dummydriver = {
+ "dummy", /* driver name */
+ DR_LOCAL|DR_MAIL, /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ dummy_valid, /* mailbox is valid for us */
+ dummy_parameters, /* manipulate parameters */
+ dummy_scan, /* scan mailboxes */
+ dummy_list, /* list mailboxes */
+ dummy_lsub, /* list subscribed mailboxes */
+ dummy_subscribe, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ dummy_delete, /* delete mailbox */
+ dummy_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ dummy_open, /* open mailbox */
+ dummy_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message structure */
+ NIL, /* fetch header */
+ NIL, /* fetch text */
+ NIL, /* fetch message data */
+ NIL, /* unique identifier */
+ NIL, /* message number from UID */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ dummy_ping, /* ping mailbox to see if still alive */
+ dummy_check, /* check for new messages */
+ dummy_expunge, /* expunge deleted messages */
+ dummy_copy, /* copy messages to another mailbox */
+ dummy_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+
+ /* prototype stream */
+MAILSTREAM dummyproto = {&dummydriver};
+
+/* Dummy validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *dummy_valid (char *name)
+{
+ char *s,*t,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* must be valid local mailbox */
+ if (name && *name && (*name != '{') && (s = mailboxfile (tmp,name))) {
+ /* indeterminate INBOX */
+ if (!*s) return &dummydriver;
+ /* remove trailing \ */
+ if ((t = strrchr (s,'\\')) && !t[1]) *t = '\0';
+ if (!stat (s,&sbuf)) switch (sbuf.st_mode & S_IFMT) {
+ case S_IFREG: /* file */
+ case S_IFDIR: /* future use */
+ return &dummydriver;
+ }
+ }
+ return NIL;
+}
+
+
+/* Dummy manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *dummy_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* Dummy scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ char *s,test[MAILTMPLEN],file[MAILTMPLEN];
+ long i = 0;
+ if (!pat || !*pat) { /* empty pattern? */
+ if (dummy_canonicalize (test,ref,"*")) {
+ /* tie off name at root */
+ if (s = strchr (test,'\\')) *++s = '\0';
+ else test[0] = '\0';
+ dummy_listed (stream,'\\',test,LATT_NOSELECT,NIL);
+ }
+ }
+ /* get canonical form of name */
+ else if (dummy_canonicalize (test,ref,pat)) {
+ /* found any wildcards? */
+ if (s = strpbrk (test,"%*")) {
+ /* yes, copy name up to that point */
+ strncpy (file,test,(size_t) (i = s - test));
+ file[i] = '\0'; /* tie off */
+ }
+ else strcpy (file,test); /* use just that name then */
+ /* find directory name */
+ if (s = strrchr (file,'\\')) {
+ *++s = '\0'; /* found, tie off at that point */
+ s = file;
+ }
+ /* silly case */
+ else if (file[0] == '#') s = file;
+ /* do the work */
+ dummy_list_work (stream,s,test,contents,0);
+ if (pmatch ("INBOX",test)) /* always an INBOX */
+ dummy_listed (stream,NIL,"INBOX",LATT_NOINFERIORS,contents);
+ }
+}
+
+/* Dummy list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ dummy_scan (stream,ref,pat,NIL);
+}
+
+
+/* Dummy list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ void *sdb = NIL;
+ char *s,*t,test[MAILTMPLEN];
+ int showuppers = pat[strlen (pat) - 1] == '%';
+ /* get canonical form of name */
+ if (dummy_canonicalize (test,ref,pat) && (s = sm_read (&sdb))) do
+ if (*s != '{') {
+ if (pmatch_full (s,test,'\\')) {
+ if (pmatch (s,"INBOX")) mm_lsub (stream,NIL,s,LATT_NOINFERIORS);
+ else mm_lsub (stream,'\\',s,NIL);
+ }
+ else while (showuppers && (t = strrchr (s,'\\'))) {
+ *t = '\0'; /* tie off the name */
+ if (pmatch_full (s,test,'\\')) mm_lsub (stream,'\\',s,LATT_NOSELECT);
+ }
+ }
+ while (s = sm_read (&sdb)); /* until no more subscriptions */
+}
+
+
+/* Dummy subscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to add to subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_subscribe (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* must be valid local mailbox */
+ if ((s = mailboxfile (tmp,mailbox)) && *s && !stat (s,&sbuf) &&
+ ((sbuf.st_mode & S_IFMT) == S_IFREG)) return sm_subscribe (mailbox);
+ sprintf (tmp,"Can't subscribe %.80s: not a mailbox",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+}
+
+/* Dummy list mailboxes worker routine
+ * Accepts: mail stream
+ * directory name to search
+ * search pattern
+ * string to scan
+ * search level
+ */
+
+void dummy_list_work (MAILSTREAM *stream,char *dir,char *pat,char *contents,
+ long level)
+{
+ unsigned long i = 1;
+ FILEFINDBUF3 f;
+ HDIR hd = HDIR_CREATE;
+ struct stat sbuf;
+ char tmp[MAILTMPLEN];
+ /* punt if bogus name */
+ if (!mailboxdir (tmp,dir,NIL)) return;
+ /* make directory wildcard */
+ strcat (tmp,(tmp[strlen (tmp) -1] == '\\') ? "*.*" : "\\*.*");
+ /* do nothing if can't open directory */
+ if (!DosFindFirst (tmp,&hd,FILE_NORMAL,&f,sizeof (f),&i,FIL_STANDARD)) {
+ /* list it if not at top-level */
+ if (!level && dir && pmatch_full (dir,pat,'\\'))
+ dummy_listed (stream,'\\',dir,LATT_NOSELECT,contents);
+ /* scan directory */
+ if (!dir || dir[strlen (dir) -1] == '\\') do {
+ if (((f.name[0] != '.') ||
+ (f.name[1] && ((f.name[1] != '.') || f.name[2]))) &&
+ (strlen (f.name) <= NETMAXMBX)) {
+ /* see if name is useful */
+ if (dir) sprintf (tmp,"%s%s",dir,f.name);
+ else strcpy (tmp,f.name);
+ /* make sure useful and can get info */
+ if ((pmatch_full (tmp,pat,'\\') ||
+ pmatch_full (strcat (tmp,"\\"),pat,'\\') ||
+ dmatch (tmp,pat,'\\')) &&
+ mailboxdir (tmp,dir,f.name) && tmp[0] && !stat (tmp,&sbuf)) {
+ /* now make name we'd return */
+ if (dir) sprintf (tmp,"%s%s",dir,f.name);
+ else strcpy (tmp,f.name);
+ /* only interested in file type */
+ switch (sbuf.st_mode & S_IFMT) {
+ case S_IFDIR: /* directory? */
+ if (pmatch_full (tmp,pat,'\\')) {
+ if (!dummy_listed (stream,'\\',tmp,LATT_NOSELECT,contents))break;
+ strcat (tmp,"\\");/* set up for dmatch call */
+ }
+ /* try again with trailing \ */
+ else if (pmatch_full (strcat (tmp,"\\"),pat,'\\') &&
+ !dummy_listed (stream,'\\',tmp,LATT_NOSELECT,contents))
+ break;
+ if (dmatch (tmp,pat,'\\') &&
+ (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL)))
+ dummy_list_work (stream,tmp,pat,contents,level+1);
+ break;
+ case S_IFREG: /* ordinary name */
+ if (pmatch_full (tmp,pat,'\\') && !pmatch ("INBOX",tmp))
+ dummy_listed (stream,'\\',tmp,LATT_NOINFERIORS,contents);
+ break;
+ }
+ }
+ }
+ i = 1;
+ } while (!DosFindNext (h,&f,sizeof (f),&i));
+ }
+}
+
+/* Mailbox found
+ * Accepts: hierarchy delimiter
+ * mailbox name
+ * attributes
+ * contents to search before calling mm_list()
+ * Returns: T, always
+ */
+
+#define BUFSIZE 4*MAILTMPLEN
+
+long dummy_listed (MAILSTREAM *stream,char delimiter,char *name,
+ long attributes,char *contents)
+{
+ struct stat sbuf;
+ int fd;
+ long csiz,ssiz,bsiz;
+ char *s,*buf,tmp[MAILTMPLEN];
+ if (contents) { /* want to search contents? */
+ /* forget it if can't select or open */
+ if ((attributes & LATT_NOSELECT) || !(csiz = strlen (contents)) ||
+ !(s = dummy_file (tmp,name)) || stat (s,&sbuf) ||
+ (csiz > sbuf.st_size) || ((fd = open (tmp,O_RDONLY,NIL)) < 0))
+ return T;
+ /* get buffer including slop */
+ buf = (char *) fs_get (BUFSIZE + (ssiz = 4 * ((csiz / 4) + 1)) + 1);
+ memset (buf,'\0',ssiz); /* no slop area the first time */
+ while (sbuf.st_size) { /* until end of file */
+ read (fd,buf+ssiz,bsiz = min (sbuf.st_size,BUFSIZE));
+ if (search ((unsigned char *) buf,bsiz+ssiz,
+ (unsigned char *) contents,csiz)) break;
+ memcpy (buf,buf+BUFSIZE,ssiz);
+ sbuf.st_size -= bsiz; /* note that we read that much */
+ }
+ fs_give ((void **) &buf); /* flush buffer */
+ close (fd); /* finished with file */
+ if (!sbuf.st_size) return T;/* not found */
+ }
+ /* notify main program */
+ mm_list (stream,delimiter,name,attributes);
+ return T;
+}
+
+/* Dummy create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create (MAILSTREAM *stream,char *mailbox)
+{
+ char tmp[MAILTMPLEN];
+ if (compare_cstring (mailbox,"INBOX") && dummy_file (tmp,mailbox))
+ return dummy_create_path (stream,tmp,NIL);
+ sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+}
+
+
+/* Dummy create path
+ * Accepts: mail stream
+ * path name to create
+ * directory mode
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode)
+{
+ struct stat sbuf;
+ char c,*s,tmp[MAILTMPLEN];
+ int fd;
+ long ret = NIL;
+ char *t = strrchr (path,'\\');
+ char *pt = (path[1] == ':') ? path + 2 : path;
+ int wantdir = t && !t[1];
+ if (wantdir) *t = '\0'; /* flush trailing delimiter for directory */
+ /* found superior to this name? */
+ if ((s = strrchr (pt,'\\')) && (s != pt)) {
+ strncpy (tmp,path,(size_t) (s - path));
+ tmp[s - path] = '\0'; /* make directory name for stat */
+ c = *++s; /* tie off in case need to recurse */
+ *s = '\0';
+ /* name doesn't exist, create it */
+ if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,path,dirmode)) return NIL;
+ *s = c; /* restore full name */
+ }
+ if (wantdir) { /* want to create directory? */
+ ret = !mkdir (path);
+ *t = '\\'; /* restore directory delimiter */
+ }
+ /* create file */
+ else if ((fd = open (path,O_WRONLY|O_CREAT|O_EXCL,S_IREAD|S_IWRITE)) >= 0)
+ ret = !close (fd); /* close file */
+ if (!ret) { /* error? */
+ sprintf (tmp,"Can't create mailbox node %.80s: %.80s",path,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+ return ret; /* return status */
+}
+
+/* Dummy delete mailbox
+ * Accepts: mail stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_delete (MAILSTREAM *stream,char *mailbox)
+{
+ struct stat sbuf;
+ char *s,tmp[MAILTMPLEN];
+ if (!(s = dummy_file (tmp,mailbox))) {
+ sprintf (tmp,"Can't delete - invalid name: %.80s",s);
+ mm_log (tmp,ERROR);
+ }
+ /* no trailing \ */
+ if ((s = strrchr (tmp,'\\')) && !s[1]) *s = '\0';
+ if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) == S_IFDIR) ?
+ rmdir (tmp) : unlink (tmp)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %.80s",mailbox,strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ return T; /* return success */
+}
+
+
+/* Mail rename mailbox
+ * Accepts: mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ struct stat sbuf;
+ char c,*s,tmp[MAILTMPLEN],mbx[MAILTMPLEN],oldname[MAILTMPLEN];
+ long ret = NIL;
+ /* no trailing \ allowed */
+ if (!dummy_file (oldname,old) || !(s = dummy_file (mbx,newname)) ||
+ ((s = strrchr (s,'\\')) && !s[1])) {
+ sprintf (mbx,"Can't rename %.80s to %.80s: invalid name",old,newname);
+ mm_log (mbx,ERROR);
+ return NIL;
+ }
+ /* found superior to destination name? */
+ if (s && (s != mbx) && ((mbx[1] != ':') || (s != mbx + 2))) {
+ c = s[1]; /* remember character after delimiter */
+ *s = s[1] = '\0'; /* tie off name at delimiter */
+ /* name doesn't exist, create it */
+ if (stat (mbx,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
+ *s = '\\'; /* restore delimiter */
+ if (!dummy_create (stream,mbx)) return NIL;
+ }
+ else *s = '\\'; /* restore delimiter */
+ s[1] = c; /* restore character after delimiter */
+ }
+ /* rename of non-ex INBOX creates dest */
+ if (!compare_cstring (old,"INBOX") && stat (oldname,&sbuf))
+ return dummy_create (NIL,mbx);
+ if (rename (oldname,mbx)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",old,newname,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ return LONGT; /* return success */
+}
+
+/* Dummy open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *dummy_open (MAILSTREAM *stream)
+{
+ int fd;
+ char err[MAILTMPLEN],tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* OP_PROTOTYPE call */
+ if (!stream) return &dummyproto;
+ err[0] = '\0'; /* no error message yet */
+ /* can we open the file? */
+ if (!dummy_file (tmp,stream->mailbox))
+ sprintf (err,"Can't open this name: %.80s",stream->mailbox);
+ else if ((fd = open (tmp,O_RDONLY,NIL)) < 0) {
+ /* no, error unless INBOX */
+ if (compare_cstring (stream->mailbox,"INBOX"))
+ sprintf (err,"%.80s: %.80s",strerror (errno),stream->mailbox);
+ }
+ else { /* file had better be empty then */
+ fstat (fd,&sbuf); /* sniff at its size */
+ close (fd);
+ if (sbuf.st_size) /* bogus format if non-empty */
+ sprintf (err,"%.80s (file %.80s) is not in valid mailbox format",
+ stream->mailbox,tmp);
+ }
+ if (err[0]) { /* if an error happened */
+ mm_log (err,stream->silent ? WARN : ERROR);
+ return NIL;
+ }
+ else if (!stream->silent) { /* only if silence not requested */
+ mail_exists (stream,0); /* say there are 0 messages */
+ mail_recent (stream,0); /* and certainly no recent ones! */
+ stream->uid_validity = time (0);
+ }
+ stream->inbox = T; /* note that it's an INBOX */
+ return stream; /* return success */
+}
+
+
+/* Dummy close
+ * Accepts: MAIL stream
+ * options
+ */
+
+void dummy_close (MAILSTREAM *stream,long options)
+{
+ /* return silently */
+}
+
+/* Dummy ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long dummy_ping (MAILSTREAM *stream)
+{
+ MAILSTREAM *test;
+ /* time to do another test? */
+ if (time (0) >= ((time_t) (stream->gensym + 30))) {
+ /* has mailbox format changed? */
+ if ((test = mail_open (NIL,stream->mailbox,OP_PROTOTYPE)) &&
+ (test->dtb != stream->dtb) &&
+ (test = mail_open (NIL,stream->mailbox,NIL))) {
+ /* preserve some resources */
+ test->original_mailbox = stream->original_mailbox;
+ stream->original_mailbox = NIL;
+ test->sparep = stream->sparep;
+ stream->sparep = NIL;
+ test->sequence = stream->sequence;
+ mail_close ((MAILSTREAM *) /* flush resources used by dummy stream */
+ memcpy (fs_get (sizeof (MAILSTREAM)),stream,
+ sizeof (MAILSTREAM)));
+ /* swap the streams */
+ memcpy (stream,test,sizeof (MAILSTREAM));
+ fs_give ((void **) &test);/* flush test now that copied */
+ /* make sure application knows */
+ mail_exists (stream,stream->recent = stream->nmsgs);
+ }
+ /* still hasn't changed */
+ else stream->gensym = time (0);
+ }
+ return T;
+}
+
+
+/* Dummy check mailbox
+ * Accepts: MAIL stream
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+void dummy_check (MAILSTREAM *stream)
+{
+ dummy_ping (stream); /* invoke ping */
+}
+
+
+/* Dummy expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ return LONGT;
+}
+
+/* Dummy copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * options
+ * Returns: T if copy successful, else NIL
+ */
+
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) fatal ("Impossible dummy_copy");
+ return NIL;
+}
+
+
+/* Dummy append message string
+ * Accepts: mail stream
+ * destination mailbox
+ * append callback function
+ * data for callback
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd = -1;
+ int e;
+ char tmp[MAILTMPLEN];
+ MAILSTREAM *ts = default_proto (T);
+ if (compare_cstring (mailbox,"INBOX") && dummy_file (tmp,mailbox) &&
+ ((fd = open (tmp,O_RDONLY,NIL)) < 0)) {
+ if ((e = errno) == ENOENT) /* failed, was it no such file? */
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",
+ (long) NIL);
+ sprintf (tmp,"%.80s: %.80s",strerror (e),mailbox);
+ mm_log (tmp,ERROR); /* pass up error */
+ return NIL; /* always fails */
+ }
+ if (fd >= 0) { /* found file? */
+ fstat (fd,&sbuf); /* get its size */
+ close (fd); /* toss out the fd */
+ if (sbuf.st_size) ts = NIL; /* non-empty file? */
+ }
+ if (ts) return (*ts->dtb->append) (stream,mailbox,af,data);
+ sprintf (tmp,"Indeterminate mailbox format: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+}
+
+/* Dummy mail generate file string
+ * Accepts: temporary buffer to write into
+ * mailbox name string
+ * Returns: local file string or NIL if failure
+ */
+
+char *dummy_file (char *dst,char *name)
+{
+ char *s = mailboxfile (dst,name);
+ /* return our standard inbox */
+ return (s && !*s) ? strcpy (dst,sysinbox ()) : s;
+}
+
+
+/* Dummy canonicalize name
+ * Accepts: buffer to write name
+ * reference
+ * pattern
+ * Returns: T if success, NIL if failure
+ */
+
+long dummy_canonicalize (char *tmp,char *ref,char *pat)
+{
+ unsigned long i;
+ char *s,dev[4];
+ /* initially no device */
+ dev[0] = dev[1] = dev[2] = dev[3] = '\0';
+ if (ref) switch (*ref) { /* preliminary reference check */
+ case '{': /* remote names not allowed */
+ return NIL; /* disallowed */
+ case '\0': /* empty reference string */
+ break;
+ default: /* all other names */
+ if (ref[1] == ':') { /* start with device name? */
+ dev[0] = *ref++; dev[1] = *ref++;
+ }
+ break;
+ }
+ if (pat[1] == ':') { /* device name in pattern? */
+ dev[0] = *pat++; dev[1] = *pat++;
+ ref = NIL; /* ignore reference */
+ }
+ switch (*pat) {
+ case '#': /* namespace names */
+ if (mailboxfile (tmp,pat)) strcpy (tmp,pat);
+ else return NIL; /* unknown namespace */
+ break;
+ case '{': /* remote names not allowed */
+ return NIL;
+ case '\\': /* rooted name */
+ ref = NIL; /* ignore reference */
+ break;
+ }
+ /* make sure device names are rooted */
+ if (dev[0] && (*(ref ? ref : pat) != '\\')) dev[2] = '\\';
+ /* build name */
+ sprintf (tmp,"%s%s%s",dev,ref ? ref : "",pat);
+ ucase (tmp); /* force upper case */
+ /* count wildcards */
+ for (i = 0, s = tmp; *s; *s++) if ((*s == '*') || (*s == '%')) ++i;
+ if (i > MAXWILDCARDS) { /* ridiculous wildcarding? */
+ MM_LOG ("Excessive wildcards in LIST/LSUB",ERROR);
+ return NIL;
+ }
+ return T;
+}
diff --git a/imap/src/osdep/os2/env_os2.c b/imap/src/osdep/os2/env_os2.c
new file mode 100644
index 00000000..3c679f5d
--- /dev/null
+++ b/imap/src/osdep/os2/env_os2.c
@@ -0,0 +1,318 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: OS/2 environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+static char *myLocalHost = NIL; /* local host name */
+static char *myHomeDir = NIL; /* home directory name */
+static char *myNewsrc = NIL; /* newsrc file name */
+static short no822tztext = NIL; /* disable RFC [2]822 timezone text */
+
+#include "write.c" /* include safe writing routines */
+#include "pmatch.c" /* include wildcard pattern matcher */
+
+
+/* Get all authenticators */
+
+#include "auths.c"
+
+/* Environment manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *env_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_HOMEDIR:
+ myHomeDir = cpystr ((char *) value);
+ case GET_HOMEDIR:
+ ret = (void *) myHomeDir;
+ break;
+ case SET_LOCALHOST:
+ myLocalHost = cpystr ((char *) value);
+ case GET_LOCALHOST:
+ ret = (void *) myLocalHost;
+ break;
+ case SET_NEWSRC:
+ if (myNewsrc) fs_give ((void **) &myNewsrc);
+ myNewsrc = cpystr ((char *) value);
+ case GET_NEWSRC:
+ if (!myNewsrc) { /* set news file name if not defined */
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"%s\\newsrc",myhomedir ());
+ myNewsrc = cpystr (tmp);
+ }
+ ret = (void *) myNewsrc;
+ break;
+ case SET_DISABLE822TZTEXT:
+ no822tztext = value ? T : NIL;
+ case GET_DISABLE822TZTEXT:
+ ret = (void *) (no822tztext ? VOIDT : NIL);
+ break;
+ }
+ return ret;
+}
+
+/* Write current time
+ * Accepts: destination string
+ * optional format of day-of-week prefix
+ * format of date and time
+ * flag whether to append symbolic timezone
+ */
+
+static void do_date (char *date,char *prefix,char *fmt,int suffix)
+{
+ time_t tn = time (0);
+ struct tm *t = gmtime (&tn);
+ int zone = t->tm_hour * 60 + t->tm_min;
+ int julian = t->tm_yday;
+ t = localtime (&tn); /* get local time now */
+ /* minus UTC minutes since midnight */
+ zone = t->tm_hour * 60 + t->tm_min - zone;
+ /* julian can be one of:
+ * 36x local time is December 31, UTC is January 1, offset -24 hours
+ * 1 local time is 1 day ahead of UTC, offset +24 hours
+ * 0 local time is same day as UTC, no offset
+ * -1 local time is 1 day behind UTC, offset -24 hours
+ * -36x local time is January 1, UTC is December 31, offset +24 hours
+ */
+ if (julian = t->tm_yday -julian)
+ zone += ((julian < 0) == (abs (julian) == 1)) ? -24*60 : 24*60;
+ if (prefix) { /* want day of week? */
+ sprintf (date,prefix,days[t->tm_wday]);
+ date += strlen (date); /* make next sprintf append */
+ }
+ /* output the date */
+ sprintf (date,fmt,t->tm_mday,months[t->tm_mon],t->tm_year+1900,
+ t->tm_hour,t->tm_min,t->tm_sec,zone/60,abs (zone) % 60);
+ if (suffix) { /* append timezone suffix if desired */
+ char *tz;
+ tzset (); /* get timezone from TZ environment stuff */
+ tz = tzname[daylight ? (((struct tm *) t)->tm_isdst > 0) : 0];
+ if (tz && tz[0]) {
+ char *s;
+ for (s = tz; *s; s++) if (*s & 0x80) return;
+ sprintf (date + strlen (date)," (%.50s)",tz);
+ }
+ }
+}
+
+
+/* Write current time in RFC 822 format
+ * Accepts: destination string
+ */
+
+void rfc822_date (char *date)
+{
+ do_date (date,"%s, ","%d %s %d %02d:%02d:%02d %+03d%02d",
+ no822tztext ? NIL : T);
+}
+
+
+/* Write current time in fixed-width RFC 822 format
+ * Accepts: destination string
+ */
+
+void rfc822_fixed_date (char *date)
+{
+ do_date (date,NIL,"%02d %s %4d %02d:%02d:%02d %+03d%02d",NIL);
+}
+
+
+/* Write current time in internal format
+ * Accepts: destination string
+ */
+
+void internal_date (char *date)
+{
+ do_date (date,NIL,"%02d-%s-%d %02d:%02d:%02d %+03d%02d",NIL);
+}
+
+/* Return my home directory name
+ * Returns: my home directory name
+ */
+
+char *myhomedir ()
+{
+ if (!myHomeDir) { /* get home directory name if not yet known */
+ char *s;
+ if ((s = getenv ("PINEHOME")) || (s = getenv ("HOME")) ||
+ (s = getenv ("ETC"))) {
+ myHomeDir = cpystr (s);
+ while (s = strchr (myHomeDir,'/')) *s = '\\';
+ if ((s = strrchr (myHomeDir,'\\')) && !s[1]) *s = '\0';
+ }
+ else myHomeDir = cpystr ("");
+ }
+ return myHomeDir;
+}
+
+
+/* Return mailbox file name
+ * Accepts: destination buffer
+ * mailbox name
+ * Returns: file name
+ */
+
+char *mailboxfile (char *dst,char *name)
+{
+ char *s;
+ char *ext = (char *) mail_parameters (NIL,GET_EXTENSION,NIL);
+ /* forbid extraneous extensions */
+ if ((s = strchr ((s = strrchr (name,'\\')) ? s : name,'.')) &&
+ ((ext = (char *) mail_parameters (NIL,GET_EXTENSION,NIL)) ||
+ strchr (s+1,'.'))) return NIL;
+ /* absolute path name? */
+ if ((*name == '\\') || (name[1] == ':')) strcpy (dst,name);
+ else sprintf (dst,"%s\\%s",myhomedir (),name);
+ if (ext) sprintf (dst + strlen (dst),".%s",ext);
+ return dst;
+}
+
+/* Lock file name
+ * Accepts: return buffer for file name
+ * file name
+ * locking to be placed on file if non-NIL
+ * Returns: file descriptor of lock or -1 if error
+ */
+
+int lockname (char *lock,char *fname,int op)
+{
+ int ld;
+ char c,*s;
+ if (!((s = lockdir (lock,getenv ("TEMP"),NIL)) ||
+ (s = lockdir (lock,getenv ("TMP"),NIL)) ||
+ (s = lockdir (lock,getenv ("TMPDIR"),NIL)) ||
+ /* C:\TEMP is last resort */
+ (s = lockdir (lock,defaultDrive (),"TEMP")))) {
+ mm_log ("Unable to find temporary directory",ERROR);
+ return -1;
+ }
+ /* generate file name */
+ while (c = *fname++) switch (c) {
+ case '/': case '\\': case ':':
+ *s++ = '!'; /* convert bad chars to ! */
+ break;
+ default:
+ *s++ = c;
+ break;
+ }
+ *s++ = c; /* tie off name */
+ /* get the lock */
+ if (((ld = open (lock,O_BINARY|O_RDWR|O_CREAT,S_IREAD|S_IWRITE)) >= 0) && op)
+ flock (ld,op); /* apply locking function */
+ return ld; /* return locking file descriptor */
+}
+
+/* Build lock directory, check to see if it exists
+ * Accepts: return buffer for lock directory
+ * first part of possible name
+ * optional second part
+ * Returns: pointer to end of buffer if buffer has a good name, else NIL
+ */
+
+char *lockdir (char *lock,char *first,char *last)
+{
+ struct stat sbuf;
+ char c,*s;
+ if (first && *first) { /* first part must be non-NIL */
+ /* copy first part */
+ for (s = lock; *first; c = *s++ = *first++);
+ if (last && *last) { /* copy last part if specified */
+ /* write trailing \ in case not in first */
+ if (c != '\\') *s++ = '\\';
+ while (*last) c = *s++ = *last++;
+ }
+ if (c == '\\') --s; /* delete trailing \ if any */
+ *s = '\0'; /* tie off name at this point */
+ return stat (lock,&sbuf) ? NIL : s;
+ }
+ return NIL; /* failed */
+}
+
+
+/* Unlock file descriptor
+ * Accepts: file descriptor
+ * lock file name from lockfd()
+ */
+
+void unlockfd (int fd,char *lock)
+{
+ flock (fd,LOCK_UN); /* unlock it */
+ close (fd); /* close it */
+}
+
+
+/* Determine default prototype stream to user
+ * Accepts: type (NIL for create, T for append)
+ * Returns: default prototype stream
+ */
+
+MAILSTREAM *default_proto (long type)
+{
+ extern MAILSTREAM DEFAULTPROTO;
+ return &DEFAULTPROTO; /* return default driver's prototype */
+}
+
+/* Global data */
+
+static unsigned rndm = 0; /* initial `random' number */
+
+
+/* Return random number
+ */
+
+long random ()
+{
+ if (!rndm) srand (rndm = (unsigned) time (0L));
+ return (long) rand ();
+}
+
+
+/* Emulator for BSD syslog() routine
+ * Accepts: priority
+ * message
+ * parameters
+ */
+
+void syslog (int priority,const char *message,...)
+{
+}
+
+
+/* Emulator for BSD openlog() routine
+ * Accepts: identity
+ * options
+ * facility
+ */
+
+void openlog (const char *ident,int logopt,int facility)
+{
+}
diff --git a/imap/src/osdep/os2/env_os2.h b/imap/src/osdep/os2/env_os2.h
new file mode 100644
index 00000000..de86da44
--- /dev/null
+++ b/imap/src/osdep/os2/env_os2.h
@@ -0,0 +1,62 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: OS/2 environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 14 March 1996
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Function prototypes */
+
+#include "env.h"
+
+long random (void);
+
+
+/* syslog() emulation */
+
+#define LOG_MAIL (2<<3) /* mail system */
+#define LOG_DAEMON (3<<3) /* system daemons */
+#define LOG_AUTH (4<<3) /* security/authorization messages */
+#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 signification condition */
+#define LOG_INFO 6 /* informational */
+#define LOG_DEBUG 7 /* debug-level messages */
+#define LOG_PID 0x01 /* log the pid with each message */
+#define LOG_CONS 0x02 /* log on the console if errors in sending */
+#define LOG_ODELAY 0x04 /* delay open until syslog() is called */
+#define LOG_NDELAY 0x08 /* don't delay open */
+#define LOG_NOWAIT 0x10 /* if forking to log on console, don't wait() */
+
+void openlog (const char *ident,int logopt,int facility);
+void syslog (int priority,const char *message,...);
+
+void rfc822_fixed_date (char *date);
+int lockname (char *lock,char *fname,int op);
+char *lockdir (char *lock,char *first,char *last);
+void unlockfd (int fd,char *lock);
diff --git a/imap/src/osdep/os2/fs_os2.c b/imap/src/osdep/os2/fs_os2.c
new file mode 100644
index 00000000..03908328
--- /dev/null
+++ b/imap/src/osdep/os2/fs_os2.c
@@ -0,0 +1,62 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Free storage management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Get a block of free storage
+ * Accepts: size of desired block
+ * Returns: free storage block
+ */
+
+void *fs_get (size_t size)
+{
+ void *block = malloc (size ? size : (size_t) 1);
+ if (!block) fatal ("Out of memory");
+ return (block);
+}
+
+
+/* Resize a block of free storage
+ * Accepts: ** pointer to current block
+ * new size
+ */
+
+void fs_resize (void **block,size_t size)
+{
+ if (!(*block = realloc (*block,size ? size : (size_t) 1)))
+ fatal ("Can't resize memory");
+}
+
+
+/* Return a block of free storage
+ * Accepts: ** pointer to free storage block
+ */
+
+void fs_give (void **block)
+{
+ free (*block);
+ *block = NIL;
+}
diff --git a/imap/src/osdep/os2/ftl_os2.c b/imap/src/osdep/os2/ftl_os2.c
new file mode 100644
index 00000000..9e65ef55
--- /dev/null
+++ b/imap/src/osdep/os2/ftl_os2.c
@@ -0,0 +1,38 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: DOS/VMS/TOPS-20 crash management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Report a fatal error
+ * Accepts: string to output
+ */
+
+void fatal (char *string)
+{
+ mm_fatal (string); /* pass up the string */
+ abort (); /* die horribly */
+}
diff --git a/imap/src/osdep/os2/makefile.os2 b/imap/src/osdep/os2/makefile.os2
new file mode 100644
index 00000000..a2211e91
--- /dev/null
+++ b/imap/src/osdep/os2/makefile.os2
@@ -0,0 +1,109 @@
+# ========================================================================
+# Copyright 1988-2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# ========================================================================
+
+# Program: Portable C client makefile -- OS/2 version
+#
+# Author: Mark Crispin
+# Networks and Distributed Computing
+# Computing & Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, WA 98195
+# Internet: MRC@CAC.Washington.EDU
+#
+# Date: 11 May 1989
+# Last Edited: 30 August 2006
+
+
+EXTRAAUTHENTICATORS =
+EXTRADRIVERS =
+EXTRACFLAGS =
+DEFAULTAUTHENTICATORS = ext md5 pla log
+DRIVERS = imap nntp pop3 mbx mtx tenex unix
+DEFAULTDRIVER = mbx
+CFLAGS = -DOMF -DCHUNKSIZE=65536 -O2 -Zomf $(EXTRACFLAGS)
+CC = gcc
+CCLIENTLIB = cclient.lib
+
+all: $(CCLIENTLIB)
+
+.c.obj:
+ $(CC) $(CFLAGS) -o $@ -c $*.c
+
+osdep.h: os_os2.h
+ copy os_os2.h osdep.h
+ drivers.cmd $(EXTRADRIVERS) $(DRIVERS) dummy
+ auths.cmd $(EXTRAAUTHENTICATORS) $(DEFAULTAUTHENTICATORS)
+ setproto.cmd $(DEFAULTDRIVER)
+
+mail.obj: mail.h misc.h osdep.h mail.c
+
+misc.obj: mail.h misc.h misc.c
+
+fdstring.obj: mail.h misc.h osdep.h fdstring.h fdstring.c
+
+flstring.obj: mail.h misc.h osdep.h flstring.h flstring.c
+
+netmsg.obj: mail.h misc.h netmsg.h osdep.h netmsg.c
+
+newsrc.obj: mail.h misc.h newsrc.h osdep.h newsrc.c
+
+rfc822.obj: mail.h rfc822.h misc.h rfc822.c
+
+smanager.obj: mail.h misc.h smanager.c
+
+utf8.obj: mail.h misc.h osdep.h utf8.h
+
+utf8aux.obj: mail.h misc.h osdep.h utf8.h
+
+imap4r1.obj: mail.h imap4r1.h misc.h osdep.h imap4r1.c
+
+nntp.obj: mail.h nntp.h smtp.h rfc822.h misc.h osdep.h nntp.c
+
+pop3.obj: mail.h rfc822.h misc.h osdep.h pop3.c
+
+smtp.obj: mail.h smtp.h rfc822.h misc.h osdep.h smtp.c
+
+os_os2.obj: mail.h osdep.h env_os2.h fs.h ftl.h nl.h tcp.h tcp_os2.h \
+ os_os2.c fs_os2.c ftl_os2.c nl_os2.c env_os2.c tcp_os2.c \
+ mailfile.h auth_md5.c auth_log.c pmatch.c write.c
+
+mbxnt.obj: mail.h misc.h osdep.h mbxnt.c
+
+mtxnt.obj: mail.h misc.h osdep.h mtxnt.c
+
+tenexnt.obj: mail.h misc.h osdep.h tenexnt.c
+
+unixnt.obj: mail.h unixnt.h pseudo.h misc.h osdep.h unixnt.c
+
+dummyos2.obj: mail.h dummy.h misc.h osdep.h dummyos2.c
+
+pseudo.obj: pseudo.h
+
+$(CCLIENTLIB): mail.obj misc.obj fdstring.obj flstring.obj netmsg.obj \
+ newsrc.obj rfc822.obj smanager.obj utf8.obj utf8aux.obj \
+ imap4r1.obj nntp.obj pop3.obj smtp.obj os_os2.obj \
+ mbxnt.obj mtxnt.obj tenexnt.obj unixnt.obj dummynt.obj pseudo.obj
+ del $(CCLIENTLIB)
+ LIB /NOLOGO /OUT:$(CCLIENTLIB) \
+ mail.obj misc.obj fdstring.obj flstring.obj netmsg.obj \
+ newsrc.obj rfc822.obj smanager.obj utf8.obj utf8aux.obj \
+ imap4r1.obj nntp.obj pop3.obj smtp.obj os_os2.obj \
+ mbxnt.obj mtxnt.obj tenexnt.obj unixnt.obj dummynt.obj pseudo.obj
+
+clean:
+ del *.lib *.obj linkage.* osdep.* auths.c *.exe *.exp
+
+# A monument to a hack of long ago and far away...
+
+love:
+ @echo not war?
diff --git a/imap/src/osdep/os2/mbxnt.c b/imap/src/osdep/os2/mbxnt.c
new file mode 100644
index 00000000..bb3d4cc0
--- /dev/null
+++ b/imap/src/osdep/os2/mbxnt.c
@@ -0,0 +1,1694 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MBX mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 3 October 1995
+ * Last Edited: 28 September 2007
+ */
+
+
+/* FILE TIME SEMANTICS
+ *
+ * The atime is the last read time of the file.
+ * The mtime is the last flags update time of the file.
+ * The ctime is the last write time of the file.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/utime.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+
+/* Build parameters */
+
+#define HDRSIZE 2048
+
+/* MBX I/O stream local data */
+
+typedef struct mbx_local {
+ unsigned int flagcheck: 1; /* if ping should sweep for flags */
+ unsigned int expok: 1; /* if expunging OK in ping */
+ unsigned int expunged : 1; /* if one or more expunged messages */
+ int fd; /* file descriptor for I/O */
+ int ld; /* lock file descriptor */
+ int ffuserflag; /* first free user flag */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* last snarf time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ char lock[MAILTMPLEN]; /* buffer to write lock name */
+} MBXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MBXLOCAL *) stream->local)
+
+/* Function prototypes */
+
+DRIVER *mbx_valid (char *name);
+int mbx_isvalid (MAILSTREAM **stream,char *name,char *file,int *ld,char *lock,
+ long flags);
+void *mbx_parameters (long function,void *value);
+void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void mbx_list (MAILSTREAM *stream,char *ref,char *pat);
+void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mbx_create (MAILSTREAM *stream,char *mailbox);
+long mbx_delete (MAILSTREAM *stream,char *mailbox);
+long mbx_rename (MAILSTREAM *stream,char *old,char *newname);
+long mbx_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *mbx_open (MAILSTREAM *stream);
+void mbx_close (MAILSTREAM *stream,long options);
+void mbx_abort (MAILSTREAM *stream);
+void mbx_flags (MAILSTREAM *stream,char *sequence,long flags);
+char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags);
+long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long mbx_ping (MAILSTREAM *stream);
+void mbx_check (MAILSTREAM *stream);
+long mbx_expunge (MAILSTREAM *stream,char *sequence,long options);
+void mbx_snarf (MAILSTREAM *stream);
+long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+long mbx_parse (MAILSTREAM *stream);
+MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok);
+unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
+void mbx_update_header (MAILSTREAM *stream);
+void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags);
+unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size,char **hdr);
+unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed,
+ long flags);
+long mbx_flaglock (MAILSTREAM *stream);
+
+/* MBX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mbxdriver = {
+ "mbx", /* driver name */
+ DR_LOCAL|DR_MAIL|DR_CRLF|DR_LOCKING,
+ /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ mbx_valid, /* mailbox is valid for us */
+ mbx_parameters, /* manipulate parameters */
+ mbx_scan, /* scan mailboxes */
+ mbx_list, /* list mailboxes */
+ mbx_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ mbx_create, /* create mailbox */
+ mbx_delete, /* delete mailbox */
+ mbx_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ mbx_open, /* open mailbox */
+ mbx_close, /* close mailbox */
+ mbx_flags, /* fetch message "fast" attributes */
+ mbx_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mbx_header, /* fetch message header */
+ mbx_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ mbx_flag, /* modify flags */
+ mbx_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mbx_ping, /* ping mailbox to see if still alive */
+ mbx_check, /* check for new messages */
+ mbx_expunge, /* expunge deleted messages */
+ mbx_copy, /* copy messages to another mailbox */
+ mbx_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mbxproto = {&mbxdriver};
+
+/* MBX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mbx_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ int fd = mbx_isvalid (NIL,name,tmp,NIL,NIL,NIL);
+ if (fd < 0) return NIL;
+ close (fd); /* don't need the fd now */
+ return &mbxdriver;
+}
+
+
+/* MBX mail test for valid mailbox
+ * Accepts: returned stream with valid mailbox keywords
+ * mailbox name
+ * buffer to write file name
+ * returned lock fd
+ * returned lock name
+ * RW flags or NIL for readonly
+ * Returns: file descriptor if valid, NIL otherwise
+ */
+
+#define MBXISVALIDNOUID 0x1 /* RW, don't do UID action */
+#define MBXISVALIDUID 0x2 /* RW, do UID action */
+
+int mbx_isvalid (MAILSTREAM **stream,char *name,char *file,int *ld,char *lock,
+ long flags)
+{
+ int fd,upd;
+ int ret = -1;
+ unsigned long i;
+ long j,k;
+ off_t pos;
+ char c,*s,*t,hdr[HDRSIZE];
+ struct stat sbuf;
+ struct utimbuf times;
+ int error = EINVAL; /* assume invalid argument */
+ if (ld) *ld = -1; /* initially no lock */
+ /* if file, get its status */
+ if ((s = dummy_file (file,name)) && !stat (s,&sbuf) &&
+ ((sbuf.st_mode & S_IFMT) == S_IFREG) &&
+ ((fd = open (file,(flags ? O_RDWR : O_RDONLY)|O_BINARY,NIL)) >= 0)) {
+ error = -1; /* assume bogus format */
+ if (((((j = read (fd,hdr,HDRSIZE)) == HDRSIZE) && (hdr[0] == '*')) ||
+ /* locked, set byte 0 to "*", read rest */
+ ((j < 0) && (lseek (fd,1,L_SET) == 1) &&
+ (read (fd,hdr+1,HDRSIZE-1) == (HDRSIZE-1)) && (hdr[0] = '*'))) &&
+ (hdr[1] == 'm') && (hdr[2] == 'b') && (hdr[3] == 'x') &&
+ (hdr[4] == '*') && (hdr[5] == '\015') && (hdr[6] == '\012') &&
+ isxdigit (hdr[7]) && isxdigit (hdr[8]) && isxdigit (hdr[9]) &&
+ isxdigit (hdr[10]) && isxdigit (hdr[11]) && isxdigit (hdr[12]) &&
+ isxdigit (hdr[13]) && isxdigit (hdr[14]) && isxdigit (c = hdr[15]) &&
+ isxdigit (hdr[16]) && isxdigit (hdr[17]) && isxdigit (hdr[18]) &&
+ isxdigit (hdr[19]) && isxdigit (hdr[20]) && isxdigit (hdr[21]) &&
+ isxdigit (hdr[22]) && (hdr[23] == '\015') && (hdr[24] == '\012')) {
+ ret = fd; /* mbx format */
+
+ if (stream) { /* lock if making a mini-stream */
+ if (flock (fd,LOCK_SH) ||
+ (flags && ((*ld = lockname (lock,file,LOCK_EX)) < 0))) ret = -1;
+ /* reread data now that locked */
+ else if (lseek (fd,0,L_SET) ||
+ (read (fd,hdr+1,HDRSIZE-1) != (HDRSIZE-1))) ret = -1;
+ else {
+ *stream = (MAILSTREAM *) memset (fs_get (sizeof (MAILSTREAM)),0,
+ sizeof (MAILSTREAM));
+ hdr[15] = '\0'; /* tie off UIDVALIDITY */
+ (*stream)->uid_validity = strtoul (hdr+7,NIL,16);
+ hdr[15] = c; /* now get UIDLAST */
+ (*stream)->uid_last = strtoul (hdr+15,NIL,16);
+ /* parse user flags */
+ for (i = 0, s = hdr + 25;
+ (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s);
+ i++, s = t + 2) {
+ *t = '\0'; /* tie off flag */
+ if (strlen (s) <= MAXUSERFLAG)
+ (*stream)->user_flags[i] = cpystr (s);
+ }
+ /* make sure have true UIDLAST */
+ if (flags & MBXISVALIDUID) {
+ for (upd = NIL,pos = 2048, k = 0; pos < sbuf.st_size;
+ pos += (j + k)) {
+ /* read header for this message */
+ lseek (fd,pos,L_SET);
+ if ((j = read (fd,hdr,64)) >= 0) {
+ hdr[j] = '\0';
+ if ((s = strchr (hdr,'\015')) && (s[1] == '\012')) {
+ *s = '\0';
+ k = s + 2 - hdr;
+ if ((s = strchr (hdr,',')) && (j = strtol (s+1,&s,10)) &&
+ (*s == ';') && (s = strchr (s+1,'-'))) {
+ /* get UID if there is any */
+ i = strtoul (++s,&t,16);
+ if (!*t && (t == (s + 8)) && (i <= (*stream)->uid_last)) {
+ if (!i) {
+ lseek (fd,pos + s - hdr,L_SET);
+ sprintf (hdr,"%08lx",++(*stream)->uid_last);
+ write (fd,hdr,8);
+ upd = T;
+ }
+ continue;
+ }
+ }
+ }
+ ret = -1; /* error, give up */
+ *stream = mail_close (*stream);
+ pos = sbuf.st_size + 1;
+ j = k = 0;
+ }
+ }
+
+ if (upd) { /* need to update hdr with new UIDLAST? */
+ lseek (fd,15,L_SET);
+ sprintf (hdr,"%08lx",(*stream)->uid_last);
+ write (fd,hdr,8);
+ }
+ }
+ }
+ }
+ }
+ if (ret != fd) close (fd); /* close the file */
+ else lseek (fd,0,L_SET); /* else rewind to start */
+ /* \Marked status? */
+ if (sbuf.st_ctime > sbuf.st_atime) {
+ /* preserve atime and mtime */
+ times.actime = sbuf.st_atime;
+ times.modtime = sbuf.st_mtime;
+ utime (file,&times); /* set the times */
+ }
+ }
+ /* in case INBOX but not mbx format */
+ else if (((error = errno) == ENOENT) && !compare_cstring (name,"INBOX"))
+ error = -1;
+ if ((ret < 0) && ld && (*ld >= 0)) {
+ unlockfd (*ld,lock);
+ *ld = -1;
+ }
+ errno = error; /* return as last error */
+ return ret; /* return what we should */
+}
+
+/* MBX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mbx_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_ONETIMEEXPUNGEATPING:
+ if (value) ((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T;
+ case GET_ONETIMEEXPUNGEATPING:
+ if (value) ret = (void *)
+ (((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL);
+ break;
+ }
+ return ret;
+}
+
+
+/* MBX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MBX mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mbx_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MBX mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MBX mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,mbx[MAILTMPLEN],tmp[HDRSIZE];
+ long ret = NIL;
+ int i,fd;
+ if (!(s = dummy_file (mbx,mailbox))) {
+ sprintf (mbx,"Can't create %.80s: invalid name",mailbox);
+ mm_log (mbx,ERROR);
+ }
+ /* create underlying file */
+ else if (dummy_create (stream,s)) {
+ /* done if made directory */
+ if ((s = strrchr (s,'\\')) && !s[1]) return T;
+ if ((fd = open (mbx,O_WRONLY|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
+ mm_log (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else {
+ memset (tmp,'\0',HDRSIZE);/* initialize header */
+ sprintf (s = tmp,"*mbx*\015\012%08lx00000000\015\012",
+ (unsigned long) time (0));
+ for (i = 0; i < NUSERFLAGS; ++i)
+ sprintf (s += strlen (s),"%s\015\012",
+ (stream && stream->user_flags[i]) ? stream->user_flags[i] :
+ "");
+ if (write (fd,tmp,HDRSIZE) != HDRSIZE) {
+ sprintf (tmp,"Can't initialize mailbox node %.80s: %s",
+ mbx,strerror (errno));
+ mm_log (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else ret = T; /* success */
+ close (fd); /* close file */
+ }
+ }
+ return ret;
+}
+
+
+/* MBX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return mbx_rename (stream,mailbox,NIL);
+}
+
+/* MBX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = LONGT;
+ char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ if (!dummy_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'\\')) && !s[1])))) {
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ else if ((fd = open (file,O_RDWR|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get parse/append permission */
+ if ((ld = lockname (lock,file,LOCK_EX)) < 0) {
+ mm_log ("Unable to lock rename mailbox",ERROR);
+ return NIL;
+ }
+ /* lock out other users */
+ if (flock (fd,LOCK_EX|LOCK_NB)) {
+ close (fd); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ mm_log (tmp,ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return NIL;
+ }
+
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if ((s = strrchr (tmp,'\\')) && (s != tmp) &&
+ ((tmp[1] != ':') || (s != tmp + 2))) {
+ c = s[1]; /* remember character after delimiter */
+ *s = s[1] = '\0'; /* tie off name at delimiter */
+ /* name doesn't exist, create it */
+ if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
+ *s = '\\'; /* restore delimiter */
+ if (!dummy_create (stream,tmp)) ret = NIL;
+ }
+ else *s = '\\'; /* restore delimiter */
+ s[1] = c; /* restore character after delimiter */
+ }
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* pacify NTFS */
+ /* rename the file */
+ if (ret && rename (file,tmp)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ else {
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* pacify NTFS */
+ if (unlink (file)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* recreate file if renamed INBOX */
+ if (ret && !compare_cstring (old,"INBOX")) mbx_create (NIL,"INBOX");
+ return ret; /* return success */
+}
+
+/* MBX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mbx_open (MAILSTREAM *stream)
+{
+ int fd,ld;
+ short silent;
+ char tmp[MAILTMPLEN];
+ if (!stream) return &mbxproto;/* return prototype for OP_PROTOTYPE call */
+ if (stream->local) fatal ("mbx recycle stream");
+ /* canonicalize the mailbox name */
+ if (!dummy_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ }
+ if (stream->rdonly ||
+ (fd = open (tmp,O_RDWR|O_BINARY,NIL)) < 0) {
+ if ((fd = open (tmp,O_RDONLY|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ else if (!stream->rdonly) { /* got it, but readonly */
+ mm_log ("Can't get write access to mailbox, access is readonly",WARN);
+ stream->rdonly = T;
+ }
+ }
+
+ stream->local = memset (fs_get (sizeof (MBXLOCAL)),NIL,sizeof (MBXLOCAL));
+ LOCAL->fd = fd; /* bind the file */
+ LOCAL->ld = -1; /* no flaglock */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ /* get parse/append permission */
+ if ((ld = lockname (tmp,stream->mailbox,LOCK_EX)) < 0) {
+ mm_log ("Unable to lock open mailbox",ERROR);
+ return NIL;
+ }
+ flock (LOCAL->fd,LOCK_SH); /* lock the file */
+ unlockfd (ld,tmp); /* release shared parse permission */
+ LOCAL->filesize = HDRSIZE; /* initialize parsed file size */
+ LOCAL->filetime = 0; /* time not set up yet */
+ LOCAL->expok = LOCAL->flagcheck = NIL;
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ silent = stream->silent; /* defer events */
+ stream->silent = T;
+ if (mbx_ping (stream) && !stream->nmsgs)
+ mm_log ("Mailbox is empty",(long) NIL);
+ stream->silent = silent; /* now notify upper level */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,stream->recent);
+ if (!LOCAL) return NIL; /* failure if stream died */
+ stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
+ stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ stream->kwd_create = (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ?
+ NIL : T; /* can we create new user flags? */
+ return stream; /* return stream to caller */
+}
+
+/* MBX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mbx_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ /* do an expunge if requested */
+ if (options & CL_EXPUNGE) mbx_expunge (stream,NIL,NIL);
+ else { /* otherwise do a checkpoint to purge */
+ LOCAL->expok = T; /* possible expunged messages */
+ mbx_ping (stream);
+ }
+ stream->silent = silent; /* restore previous status */
+ mbx_abort (stream);
+ }
+}
+
+
+/* MBX mail abort stream
+ * Accepts: MAIL stream
+ */
+
+void mbx_abort (MAILSTREAM *stream)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ flock (LOCAL->fd,LOCK_UN); /* unlock local file */
+ close (LOCAL->fd); /* close the local file */
+ /* free local text buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+
+/* MBX mail fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ * Sniffs at file to see if some other process changed the flags
+ */
+
+void mbx_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned long i;
+ if (mbx_ping (stream) && /* ping mailbox, get new status for messages */
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence && !elt->valid)
+ mbx_elt (stream,i,NIL);
+}
+
+/* MBX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ unsigned long i;
+ char *s;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get header position, possibly header */
+ i = mbx_hdrpos (stream,msgno,length,&s);
+ if (!s) { /* mbx_hdrpos() returned header? */
+ lseek (LOCAL->fd,i,L_SET); /* no, get to header position */
+ /* is buffer big enough? */
+ if (*length > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = *length) + 1);
+ }
+ /* slurp the data */
+ read (LOCAL->fd,s = LOCAL->buf,*length);
+ }
+ s[*length] = '\0'; /* tie off string */
+ return s;
+}
+
+/* MBX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ FDDATA d;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ /* get message status */
+ elt = mbx_elt (stream,msgno,NIL);
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen && mbx_flaglock (stream)) {
+ elt->seen = T; /* mark message as seen */
+ /* recalculate status */
+ mbx_update_status (stream,msgno,NIL);
+ mm_flags (stream,msgno);
+ /* update flags */
+ mbx_flag (stream,NIL,NIL,NIL);
+ }
+ if (!LOCAL) return NIL; /* mbx_flaglock() could have aborted */
+ /* find header position */
+ i = mbx_hdrpos (stream,msgno,&j,NIL);
+ d.fd = LOCAL->fd; /* set up file descriptor */
+ d.pos = i + j;
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE;
+ INIT (bs,fd_string,&d,elt->rfc822_size - j);
+ return LONGT; /* success */
+}
+
+/* MBX mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ * Unlocks flag lock
+ */
+
+void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ struct utimbuf times;
+ struct stat sbuf;
+ /* make sure the update takes */
+ if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld >= 0)) {
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ /* update header */
+ if ((LOCAL->ffuserflag < NUSERFLAGS) &&
+ stream->user_flags[LOCAL->ffuserflag]) mbx_update_header (stream);
+ times.actime = time (0); /* make sure read comes after all that */
+ utime (stream->mailbox,&times);
+ }
+ if (LOCAL->ld >= 0) { /* unlock now */
+ unlockfd (LOCAL->ld,LOCAL->lock);
+ LOCAL->ld = -1;
+ }
+}
+
+
+/* MBX mail per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ if (mbx_flaglock (stream)) mbx_update_status (stream,elt->msgno,NIL);
+}
+
+/* MBX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long mbx_ping (MAILSTREAM *stream)
+{
+ unsigned long i,pos;
+ long ret = NIL;
+ int ld;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ struct stat sbuf;
+ if (stream && LOCAL) { /* only if stream already open */
+ ret = LONGT; /* assume OK */
+ fstat (LOCAL->fd,&sbuf); /* get current file poop */
+ /* allow expunge if permitted at ping */
+ if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T;
+ /* if external modification */
+ if (LOCAL->filetime && (LOCAL->filetime < sbuf.st_mtime))
+ LOCAL->flagcheck = T; /* upgrade to flag checking */
+ /* new mail or flagcheck handling needed? */
+ if (((sbuf.st_size - LOCAL->filesize) || LOCAL->flagcheck ||
+ !stream->nmsgs) &&
+ ((ld = lockname (lock,stream->mailbox,LOCK_EX)) >= 0)) {
+ if (!LOCAL->flagcheck) ret = mbx_parse (stream);
+ /* sweep mailbox for changed message status */
+ else if (ret = mbx_parse (stream)) {
+ unsigned long recent = 0;
+ LOCAL->filetime = sbuf.st_mtime;
+ for (i = 1; i <= stream->nmsgs; )
+ if (elt = mbx_elt (stream,i,LOCAL->expok)) {
+ if (elt->recent) ++recent;
+ ++i;
+ }
+ mail_recent (stream,recent);
+ LOCAL->flagcheck = NIL; /* got all the updates */
+ }
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ if (ret) { /* must still be alive */
+ if (!LOCAL->expunged) /* look for holes if none known yet */
+ for (i = 1, pos = HDRSIZE;
+ !LOCAL->expunged && (i <= stream->nmsgs);
+ i++, pos += elt->private.special.text.size + elt->rfc822_size)
+ if ((elt = mail_elt (stream,i))->private.special.offset != pos)
+ LOCAL->expunged = T;/* found a hole */
+ /* burp any holes */
+ if (LOCAL->expunged && !stream->rdonly) {
+ if (mbx_rewrite (stream,&i,NIL)) fatal ("expunge on check");
+ if (i) { /* any space reclaimed? */
+ LOCAL->expunged = NIL;/* no more pending expunge */
+ sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",i);
+ mm_log (LOCAL->buf,(long) NIL);
+ }
+ }
+ LOCAL->expok = NIL; /* no more expok */
+ }
+ }
+ return ret; /* return result of the parse */
+}
+
+/* MBX mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void mbx_check (MAILSTREAM *stream)
+{
+ if (LOCAL) LOCAL->expok = T; /* mark that a check is desired */
+ if (mbx_ping (stream)) mm_log ("Check completed",(long) NIL);
+}
+
+
+/* MBX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T if success, NIL if failure
+ */
+
+long mbx_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ unsigned long nexp,reclaimed;
+ if (ret = sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) {
+ if (!mbx_ping (stream)); /* do nothing if stream dead */
+ else if (stream->rdonly) /* won't do on readonly files! */
+ mm_log ("Expunge ignored on readonly mailbox",WARN);
+ /* if expunged any messages */
+ else if (nexp = mbx_rewrite (stream,&reclaimed,sequence ? -1 : 1)) {
+ sprintf (LOCAL->buf,"Expunged %lu messages",nexp);
+ mm_log (LOCAL->buf,(long) NIL);
+ }
+ else if (reclaimed) { /* or if any prior expunged space reclaimed */
+ sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed);
+ mm_log (LOCAL->buf,(long) NIL);
+ }
+ else mm_log ("No messages deleted, so no update needed",(long) NIL);
+ }
+ return ret;
+}
+
+/* MBX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ struct utimbuf times;
+ MESSAGECACHE *elt;
+ unsigned long i,j,k,m;
+ long ret = LONGT;
+ int fd,ld;
+ char *s,*t,file[MAILTMPLEN],lock[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ MAILSTREAM *dstream = NIL;
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* make sure valid mailbox */
+ if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
+ cu ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid MBX-format mailbox name: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a MBX-format mailbox: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ /* got file? */
+ if ((fd = open (dummy_file (file,mailbox),O_RDWR|O_CREAT|O_BINARY,
+ S_IREAD|S_IWRITE)) < 0) {
+ sprintf (LOCAL->buf,"Unable to open copy mailbox: %s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ mm_critical (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
+
+ /* for each requested message */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.special.text.size,L_SET);
+ mail_date(LOCAL->buf,elt);/* build target header */
+ /* get target keyword mask */
+ for (j = elt->user_flags, k = 0; j; )
+ if (s = stream->user_flags[find_rightmost_bit (&j)])
+ for (m = 0; (m < NUSERFLAGS) && (t = dstream->user_flags[m]); m++)
+ if (!compare_cstring (s,t) && (k |= 1 << m)) break;
+ sprintf (LOCAL->buf+strlen(LOCAL->buf),",%lu;%08lx%04x-%08lx\015\012",
+ elt->rfc822_size,k,(unsigned)
+ ((fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)),cu ? ++dstream->uid_last : 0);
+ /* write target header */
+ if (ret = (write (fd,LOCAL->buf,strlen (LOCAL->buf)) > 0)) {
+ for (k = elt->rfc822_size; ret && (j = min (k,LOCAL->buflen)); k -= j){
+ read (LOCAL->fd,LOCAL->buf,j);
+ ret = write (fd,LOCAL->buf,j) >= 0;
+ }
+ if (cu) { /* need to pass back new UID? */
+ mail_append_set (source,mail_uid (stream,i));
+ mail_append_set (dest,dstream->uid_last);
+ }
+ }
+ }
+
+ /* make sure all the updates take */
+ if (!(ret && (ret = !fsync (fd)))) {
+ sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ }
+ if (cu && ret) { /* return sets if doing COPYUID */
+ (*cu) (stream,mailbox,dstream->uid_validity,source,dest);
+ lseek (fd,15,L_SET); /* update UIDLAST */
+ sprintf (LOCAL->buf,"%08lx",dstream->uid_last);
+ write (fd,LOCAL->buf,8);
+ }
+ else { /* flush any sets we may have built */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ /* set atime to now-1 if successful copy */
+ if (ret) times.actime = time (0) - 1;
+ /* else preserved \Marked status */
+ else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
+ sbuf.st_atime : time (0);
+ times.modtime = sbuf.st_mtime;/* preserve mtime */
+ utime (file,&times); /* set the times */
+ close (fd); /* close the file */
+ mm_nocritical (stream); /* release critical */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE) && mbx_flaglock (stream)) {
+ for (i = 1; i <= stream->nmsgs; i++) if (mail_elt (stream,i)->sequence) {
+ /* mark message deleted */
+ mbx_elt (stream,i,NIL)->deleted = T;
+ /* recalculate status */
+ mbx_update_status (stream,i,NIL);
+ }
+ /* update flags */
+ mbx_flag (stream,NIL,NIL,NIL);
+ }
+ if (dstream != stream) mail_close (dstream);
+ return ret;
+}
+
+/* MBX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd,ld;
+ char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ struct utimbuf times;
+ FILE *df;
+ MESSAGECACHE elt;
+ long f;
+ unsigned long i,uf;
+ STRING *message;
+ long ret = NIL;
+ MAILSTREAM *dstream = NIL;
+ appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ /* make sure valid mailbox */
+ /* make sure valid mailbox */
+ if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
+ au ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* can create INBOX here */
+ mbx_create (dstream = stream ? stream : &mbxproto,"INBOX");
+ if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
+ au ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid MBX-format mailbox name: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MBX-format mailbox: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+
+ /* get first message */
+ if (!(*af) (dstream,data,&flags,&date,&message)) close (fd);
+ else if (!(df = fdopen (fd,"r+b"))) {
+ MM_LOG ("Unable to reopen append mailbox",ERROR);
+ close (fd);
+ }
+ else {
+ mm_critical (dstream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ fseek (df,sbuf.st_size,SEEK_SET);
+ errno = 0;
+ for (ret = LONGT; ret && message; ) {
+ if (!SIZE (message)) { /* guard against zero-length */
+ mm_log ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ f = mail_parse_flags (dstream,flags,&uf);
+ if (date) { /* parse date if given */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ mm_log (tmp,ERROR);
+ ret = NIL; /* mark failure */
+ break;
+ }
+ mail_date (tmp,&elt); /* write preseved date */
+ }
+ else internal_date (tmp); /* get current date in IMAP format */
+ /* write header */
+ if (fprintf (df,"%s,%lu;%08lx%04lx-%08lx\015\012",tmp,i = SIZE (message),
+ uf,(unsigned long) f,au ? ++dstream->uid_last : 0) < 0)
+ ret = NIL;
+ else { /* write message */
+ size_t j;
+ if (!message->cursize) SETPOS (message,GETPOS (message));
+ while (i && (j = fwrite (message->curpos,1,message->cursize,df))) {
+ i -= j;
+ SETPOS (message,GETPOS (message) + j);
+ }
+ /* get next message */
+ if (i || !(*af) (dstream,data,&flags,&date,&message)) ret = NIL;
+ else if (au) mail_append_set (dst,dstream->uid_last);
+ }
+ }
+
+ /* if error... */
+ if (!ret || (fflush (df) == EOF)) {
+ /* revert file */
+ ftruncate (fd,sbuf.st_size);
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ if (errno) {
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+ ret = NIL;
+ }
+ if (au && ret) { /* return sets if doing APPENDUID */
+ (*au) (mailbox,dstream->uid_validity,dst);
+ fseek (df,15,SEEK_SET); /* update UIDLAST */
+ fprintf (df,"%08lx",dstream->uid_last);
+ }
+ else mail_free_searchset (&dst);
+ if (ret) times.actime = time (0) - 1;
+ /* else preserve \Marked status */
+ else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
+ sbuf.st_atime : time (0);
+ /* preserve mtime */
+ times.modtime = sbuf.st_mtime;
+ utime (file,&times); /* set the times */
+ fclose (df); /* close the file */
+ mm_nocritical (dstream); /* release critical */
+ }
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ if (dstream != stream) mail_close (dstream);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* MBX mail parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long mbx_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = NIL;
+ unsigned char c,*s,*t,*x;
+ char tmp[MAILTMPLEN];
+ unsigned long i,j,k,m;
+ off_t curpos = LOCAL->filesize;
+ unsigned long nmsgs = stream->nmsgs;
+ unsigned long recent = stream->recent;
+ unsigned long lastuid = 0;
+ short dirty = NIL;
+ short added = NIL;
+ short silent = stream->silent;
+ short uidwarn = T;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %lu to %lu!",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ lseek (LOCAL->fd,0,L_SET); /* rewind file */
+ /* read internal header */
+ read (LOCAL->fd,LOCAL->buf,HDRSIZE);
+ LOCAL->buf[HDRSIZE] = '\0'; /* tie off header */
+ c = LOCAL->buf[15]; /* save first character of last UID */
+ LOCAL->buf[15] = '\0';
+ /* parse UID validity */
+ stream->uid_validity = strtoul (LOCAL->buf + 7,NIL,16);
+ LOCAL->buf[15] = c; /* restore first character of last UID */
+ /* parse last UID */
+ i = strtoul (LOCAL->buf + 15,NIL,16);
+ stream->uid_last = stream->rdonly ? max (i,stream->uid_last) : i;
+ /* parse user flags */
+ for (i = 0, s = LOCAL->buf + 25;
+ (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s);
+ i++, s = t + 2) {
+ *t = '\0'; /* tie off flag */
+ if (!stream->user_flags[i] && (strlen (s) <= MAXUSERFLAG))
+ stream->user_flags[i] = cpystr (s);
+ }
+ LOCAL->ffuserflag = (int) i; /* first free user flag */
+
+ stream->silent = T; /* don't pass up mm_exists() events yet */
+ while (sbuf.st_size - curpos){/* while there is stuff to parse */
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,L_SET);
+ if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
+ sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size,
+ i ? strerror (errno) : "no data read");
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
+ if (!((s = strchr (LOCAL->buf,'\015')) && (s[1] == '\012'))) {
+ sprintf (tmp,"Unable to find CRLF at %lu in %lu bytes, text: %.80s",
+ (unsigned long) curpos,i,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ *s = '\0'; /* tie off header line */
+ i = (s + 2) - LOCAL->buf; /* note start of text offset */
+ if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
+ sprintf (tmp,"Unable to parse internal header at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ if (!(isxdigit (t[1]) && isxdigit (t[2]) && isxdigit (t[3]) &&
+ isxdigit (t[4]) && isxdigit (t[5]) && isxdigit (t[6]) &&
+ isxdigit (t[7]) && isxdigit (t[8]) && isxdigit (t[9]) &&
+ isxdigit (t[10]) && isxdigit (t[11]) && isxdigit (t[12]))) {
+ sprintf (tmp,"Unable to parse message flags at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ if ((t[13] != '-') || t[22] ||
+ !(isxdigit (t[14]) && isxdigit (t[15]) && isxdigit (t[16]) &&
+ isxdigit (t[17]) && isxdigit (t[18]) && isxdigit (t[19]) &&
+ isxdigit (t[20]) && isxdigit (t[21]))) {
+ sprintf (tmp,"Unable to parse message UID at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+
+ *s++ = '\0'; *t++ = '\0'; /* break up fields */
+ /* get message size */
+ if (!(j = strtoul (s,(char **) &x,10)) && (!(x && *x))) {
+ sprintf (tmp,"Unable to parse message size at %lu: %.80s,%.80s;%.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf,(char *) s,
+ (char *) t);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ /* make sure didn't run off end of file */
+ if (((off_t) (curpos + i + j)) > sbuf.st_size) {
+ sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
+ (unsigned long) curpos,(unsigned long) (curpos + i + j),
+ (unsigned long) sbuf.st_size);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ /* parse UID */
+ if ((m = strtoul (t+13,NIL,16)) &&
+ ((m <= lastuid) || (m > stream->uid_last))) {
+ if (uidwarn) {
+ sprintf (tmp,"Invalid UID %08lx in message %lu, rebuilding UIDs",
+ m,nmsgs+1);
+ mm_log (tmp,WARN);
+ uidwarn = NIL;
+ /* restart UID validity */
+ stream->uid_validity = (unsigned long) time (0);
+ }
+ m = 0; /* lose this UID */
+ dirty = T; /* mark dirty, set new lastuid */
+ stream->uid_last = lastuid;
+ }
+
+ t[12] = '\0'; /* parse system flags */
+ if ((k = strtoul (t+8,NIL,16)) & fEXPUNGED) {
+ if (m) lastuid = m; /* expunge message, update last UID seen */
+ else { /* no UID assigned? */
+ lastuid = ++stream->uid_last;
+ dirty = T;
+ }
+ }
+ else { /* not expunged, swell the cache */
+ added = T; /* note that a new message was added */
+ mail_exists (stream,++nmsgs);
+ /* instantiate an elt for this message */
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ /* parse the date */
+ if (!mail_parse_date (elt,LOCAL->buf)) {
+ sprintf (tmp,"Unable to parse message date at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* and internal header size */
+ elt->private.special.text.size = i;
+ /* header size not known yet */
+ elt->private.msg.header.text.size = 0;
+ elt->rfc822_size = j; /* note message size */
+ /* calculate system flags */
+ if (k & fSEEN) elt->seen = T;
+ if (k & fDELETED) elt->deleted = T;
+ if (k & fFLAGGED) elt->flagged = T;
+ if (k & fANSWERED) elt->answered = T;
+ if (k & fDRAFT) elt->draft = T;
+ t[8] = '\0'; /* get user flags value */
+ elt->user_flags = strtoul (t,NIL,16);
+ /* UID already assigned? */
+ if (!(elt->private.uid = m) || !(k & fOLD)) {
+ elt->recent = T; /* no, mark as recent */
+ ++recent; /* count up a new recent message */
+ dirty = T; /* and must rewrite header */
+ /* assign new UID */
+ if (!elt->private.uid) elt->private.uid = ++stream->uid_last;
+ mbx_update_status (stream,elt->msgno,NIL);
+ }
+ /* update last parsed UID */
+ lastuid = elt->private.uid;
+ }
+ curpos += i + j; /* update position */
+ }
+
+ if (dirty && !stream->rdonly){/* update header */
+ mbx_update_header (stream);
+ fsync (LOCAL->fd); /* make sure all the UID updates take */
+ }
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
+ LOCAL->filetime = sbuf.st_mtime;
+ if (added && !stream->rdonly){/* make sure atime updated */
+ struct utimbuf times;
+ times.actime = time (0);
+ times.modtime = LOCAL->filetime;
+ utime (stream->mailbox,&times);
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return LONGT; /* return the winnage */
+}
+
+/* MBX get cache element with status updating from file
+ * Accepts: MAIL stream
+ * message number
+ * expunge OK flag
+ * Returns: cache element
+ */
+
+MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok)
+{
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
+ old.answered = elt->answered; old.draft = elt->draft;
+ old.user_flags = elt->user_flags;
+ /* get new flags */
+ if (mbx_read_flags (stream,elt) && expok) {
+ mail_expunged (stream,elt->msgno);
+ return NIL; /* return this message was expunged */
+ }
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
+ (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
+ mm_flags (stream,msgno); /* let top level know */
+ return elt;
+}
+
+/* MBX read flags from file
+ * Accepts: MAIL stream
+ * cache element
+ * Returns: non-NIL if message expunged
+ */
+
+unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ unsigned long i;
+ struct stat sbuf;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* make sure file size is good */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag read!",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ fatal (LOCAL->buf);
+ }
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 24,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,14) < 0) {
+ sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) {
+ LOCAL->buf[14] = '\0'; /* tie off buffer for error message */
+ sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s",
+ elt->msgno,elt->private.special.offset,
+ elt->private.special.text.size,(char *) LOCAL->buf);
+ fatal (LOCAL->buf+50);
+ }
+ LOCAL->buf[13] = '\0'; /* tie off buffer */
+ /* calculate system flags */
+ i = strtoul (LOCAL->buf+9,NIL,16);
+ elt->seen = i & fSEEN ? T : NIL;
+ elt->deleted = i & fDELETED ? T : NIL;
+ elt->flagged = i & fFLAGGED ? T : NIL;
+ elt->answered = i & fANSWERED ? T : NIL;
+ elt->draft = i & fDRAFT ? T : NIL;
+ LOCAL->expunged |= i & fEXPUNGED ? T : NIL;
+ LOCAL->buf[9] = '\0'; /* tie off flags */
+ /* get user flags value */
+ elt->user_flags = strtoul (LOCAL->buf+1,NIL,16);
+ elt->valid = T; /* have valid flags now */
+ return i & fEXPUNGED;
+}
+
+/* MBX update header
+ * Accepts: MAIL stream
+ */
+
+#define NTKLUDGEOFFSET 7
+
+void mbx_update_header (MAILSTREAM *stream)
+{
+ int i;
+ char *s = LOCAL->buf;
+ memset (s,'\0',HDRSIZE); /* initialize header */
+ sprintf (s,"*mbx*\015\012%08lx%08lx\015\012",
+ stream->uid_validity,stream->uid_last);
+ for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; ++i)
+ sprintf (s += strlen (s),"%s\015\012",stream->user_flags[i]);
+ LOCAL->ffuserflag = i; /* first free user flag */
+ /* can we create more user flags? */
+ stream->kwd_create = (i < NUSERFLAGS) ? T : NIL;
+ /* write reserved lines */
+ while (i++ < NUSERFLAGS) strcat (s,"\015\012");
+ while (T) { /* rewind file */
+ lseek (LOCAL->fd,NTKLUDGEOFFSET,L_SET);
+ /* write new header */
+ if (write (LOCAL->fd,LOCAL->buf + NTKLUDGEOFFSET,
+ HDRSIZE - NTKLUDGEOFFSET) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+}
+
+/* MBX update status string
+ * Accepts: MAIL stream
+ * message number
+ * flags
+ */
+
+void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ /* readonly */
+ if (stream->rdonly || !elt->valid) mbx_read_flags (stream,elt);
+ else { /* readwrite */
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* make sure file size is good */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag update!",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ fatal (LOCAL->buf);
+ }
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 24,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,14) < 0) {
+ sprintf (LOCAL->buf,"Unable to read old status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) {
+ LOCAL->buf[14] = '\0'; /* tie off buffer for error message */
+ sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s",
+ elt->msgno,elt->private.special.offset,
+ elt->private.special.text.size,(char *) LOCAL->buf);
+ fatal (LOCAL->buf+50);
+ }
+ /* print new flag string */
+ sprintf (LOCAL->buf,"%08lx%04x-%08lx",elt->user_flags,(unsigned)
+ (((elt->deleted && flags) ?
+ fEXPUNGED : (strtoul (LOCAL->buf+9,NIL,16)) & fEXPUNGED) +
+ (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft) + fOLD),elt->private.uid);
+ while (T) { /* get to that place in the file */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 23,L_SET);
+ /* write new flags and UID */
+ if (write (LOCAL->fd,LOCAL->buf,21) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+ }
+}
+
+/* MBX locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * pointer to possible returned header
+ * Returns: position of header in file
+ */
+
+#define HDRBUFLEN 16384 /* good enough for most headers */
+#define SLOP 4 /* CR LF CR LF */
+
+unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size,char **hdr)
+{
+ unsigned long siz,done;
+ long i;
+ unsigned char *s,*t,*te;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ unsigned long ret = elt->private.special.offset +
+ elt->private.special.text.size;
+ if (hdr) *hdr = NIL; /* assume no header returned */
+ /* is header size known? */
+ if (*size = elt->private.msg.header.text.size) return ret;
+ /* paranoia check */
+ if (LOCAL->buflen < (HDRBUFLEN + SLOP))
+ fatal ("LOCAL->buf smaller than HDRBUFLEN");
+ lseek (LOCAL->fd,ret,L_SET); /* get to header position */
+ /* read HDRBUFLEN chunks with 4 byte slop */
+ for (done = siz = 0, s = LOCAL->buf;
+ (i = min ((long) (elt->rfc822_size - done),(long) HDRBUFLEN)) &&
+ (read (LOCAL->fd,s,i) == i);
+ done += i, siz += (t - LOCAL->buf) - SLOP, s = LOCAL->buf + SLOP) {
+ te = (t = s + i) - 12; /* calculate end of fast scan */
+ /* fast scan for CR */
+ for (s = LOCAL->buf; s < te;)
+ if (((*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
+ (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
+ (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
+ (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015')) &&
+ (*s == '\012') && (*++s == '\015') && (*++s == '\012')) {
+ *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf);
+ if (hdr) *hdr = LOCAL->buf;
+ return ret;
+ }
+ for (te = t - 3; (s < te);) /* final character-at-a-time scan */
+ if ((*s++ == '\015') && (*s == '\012') && (*++s == '\015') &&
+ (*++s == '\012')) {
+ *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf);
+ if (hdr) *hdr = LOCAL->buf;
+ return ret;
+ }
+ if (i <= SLOP) break; /* end of data */
+ /* slide over last 4 bytes */
+ memmove (LOCAL->buf,t - SLOP,SLOP);
+ hdr = NIL; /* can't return header this way */
+ }
+ /* not found: header consumes entire message */
+ elt->private.msg.header.text.size = *size = elt->rfc822_size;
+ if (hdr) *hdr = LOCAL->buf; /* possibly return header too */
+ return ret;
+}
+
+/* MBX mail rewrite mailbox
+ * Accepts: MAIL stream
+ * pointer to return reclaimed size
+ * flags (0 = no expunge, 1 = expunge deleted, -1 = expunge sequence)
+ * Returns: number of expunged messages
+ */
+
+unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed,
+ long flags)
+{
+ struct utimbuf times;
+ struct stat sbuf;
+ off_t pos,ppos;
+ int ld;
+ unsigned long i,j,k,m,delta;
+ unsigned long n = *reclaimed = 0;
+ unsigned long recent = 0;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ /* get parse/append permission */
+ if ((ld = lockname (lock,stream->mailbox,LOCK_EX)) < 0) {
+ mm_log ("Unable to lock expunge mailbox",ERROR);
+ return 0;
+ }
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime && !LOCAL->flagcheck &&
+ (LOCAL->filetime < sbuf.st_mtime)) LOCAL->flagcheck = T;
+ if (!mbx_parse (stream)) { /* make sure see any newly-arrived messages */
+ unlockfd (ld,lock); /* failed?? */
+ return 0;
+ }
+ if (LOCAL->flagcheck) { /* sweep flags if need flagcheck */
+ LOCAL->filetime = sbuf.st_mtime;
+ for (i = 1; i <= stream->nmsgs; ++i) mbx_elt (stream,i,NIL);
+ LOCAL->flagcheck = NIL;
+ }
+
+ /* get exclusive access */
+ if (!flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
+ mm_critical (stream); /* go critical */
+ for (i = 1,delta = 0,pos = ppos = HDRSIZE; i <= stream->nmsgs; ) {
+ /* note if message not at predicted location */
+ if (m = (elt = mbx_elt (stream,i,NIL))->private.special.offset - ppos) {
+ ppos = elt->private.special.offset;
+ *reclaimed += m; /* note reclaimed message space */
+ delta += m; /* and as expunge delta */
+ }
+ /* number of bytes to smash or preserve */
+ ppos += (k = elt->private.special.text.size + elt->rfc822_size);
+ /* if need to expunge this message*/
+ if (flags && elt->deleted && ((flags > 0) || elt->sequence)) {
+ delta += k; /* number of bytes to delete */
+ mail_expunged(stream,i);/* notify upper levels */
+ n++; /* count up one more expunged message */
+ }
+ else { /* preserved message */
+ i++; /* count this message */
+ if (elt->recent) ++recent;
+ if (delta) { /* moved, note first byte to preserve */
+ j = elt->private.special.offset;
+ do { /* read from source position */
+ m = min (k,LOCAL->buflen);
+ lseek (LOCAL->fd,j,L_SET);
+ read (LOCAL->fd,LOCAL->buf,m);
+ pos = j - delta; /* write to destination position */
+ while (T) {
+ lseek (LOCAL->fd,pos,L_SET);
+ if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+ pos += m; /* new position */
+ j += m; /* next chunk, perhaps */
+ } while (k -= m); /* until done */
+ /* note the new address of this text */
+ elt->private.special.offset -= delta;
+ }
+ /* preserved but no deleted messages yet */
+ else pos = elt->private.special.offset + k;
+ }
+ }
+ /* deltaed file size match position? */
+ if (m = (LOCAL->filesize -= delta) - pos) {
+ *reclaimed += m; /* probably an fEXPUNGED msg */
+ LOCAL->filesize = pos; /* set correct size */
+ }
+ /* truncate file after last message */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ fsync (LOCAL->fd); /* force disk update */
+ mm_nocritical (stream); /* release critical */
+ flock (LOCAL->fd,LOCK_SH); /* allow sharers again */
+ }
+
+ else { /* can't get exclusive */
+ flock (LOCAL->fd,LOCK_SH); /* recover previous shared mailbox lock */
+ /* do hide-expunge when shared */
+ if (flags) for (i = 1; i <= stream->nmsgs; ) {
+ if (elt = mbx_elt (stream,i,T)) {
+ /* make the message invisible */
+ if (elt->deleted && ((flags > 0) || elt->sequence)) {
+ mbx_update_status (stream,elt->msgno,LONGT);
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else {
+ i++; /* preserved message */
+ if (elt->recent) ++recent;
+ }
+ }
+ else n++; /* count up one more expunged message */
+ }
+ fsync (LOCAL->fd); /* force disk update */
+ }
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* reset atime to now */
+ utime (stream->mailbox,&times);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ return n; /* return number of expunged messages */
+}
+
+/* MBX mail lock for flag updating
+ * Accepts: stream
+ * Returns: T if successful, NIL if failure
+ */
+
+long mbx_flaglock (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ unsigned long i;
+ int ld;
+ char lock[MAILTMPLEN];
+ /* no-op if readonly or already locked */
+ if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld < 0)) {
+ /* lock now */
+ if ((ld = lockname (lock,stream->mailbox,LOCK_EX)) < 0) return NIL;
+ if (!LOCAL->flagcheck) { /* don't do this if flagcheck already needed */
+ if (LOCAL->filetime) { /* know previous time? */
+ fstat (LOCAL->fd,&sbuf);/* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->flagcheck = T;
+ LOCAL->filetime = 0; /* don't do this test for any other messages */
+ }
+ if (!mbx_parse (stream)) {/* parse mailbox */
+ unlockfd (ld,lock); /* shouldn't happen */
+ return NIL;
+ }
+ if (LOCAL->flagcheck) /* invalidate cache if flagcheck */
+ for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->valid = NIL;
+ }
+ LOCAL->ld = ld; /* copy to stream for subsequent calls */
+ memcpy (LOCAL->lock,lock,MAILTMPLEN);
+ }
+ return LONGT;
+}
diff --git a/imap/src/osdep/os2/mtxnt.c b/imap/src/osdep/os2/mtxnt.c
new file mode 100644
index 00000000..13f61586
--- /dev/null
+++ b/imap/src/osdep/os2/mtxnt.c
@@ -0,0 +1,1232 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MTX mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 22 May 1990
+ * Last Edited: 15 June 2007
+ */
+
+
+/* FILE TIME SEMANTICS
+ *
+ * The atime is the last read time of the file.
+ * The mtime is the last flags update time of the file.
+ * The ctime is the last write time of the file.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/utime.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+/* MTX I/O stream local data */
+
+typedef struct mtx_local {
+ unsigned int shouldcheck: 1; /* if ping should do a check instead */
+ unsigned int mustcheck: 1; /* if ping must do a check instead */
+ int fd; /* file descriptor for I/O */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* last snarf time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+} MTXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MTXLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *mtx_valid (char *name);
+int mtx_isvalid (char *name,char *file);
+void *mtx_parameters (long function,void *value);
+void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void mtx_list (MAILSTREAM *stream,char *ref,char *pat);
+void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mtx_create (MAILSTREAM *stream,char *mailbox);
+long mtx_delete (MAILSTREAM *stream,char *mailbox);
+long mtx_rename (MAILSTREAM *stream,char *old,char *newname);
+long mtx_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *mtx_open (MAILSTREAM *stream);
+void mtx_close (MAILSTREAM *stream,long options);
+void mtx_flags (MAILSTREAM *stream,char *sequence,long flags);
+char *mtx_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void mtx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long mtx_ping (MAILSTREAM *stream);
+void mtx_check (MAILSTREAM *stream);
+void mtx_snarf (MAILSTREAM *stream);
+long mtx_expunge (MAILSTREAM *stream,char *sequence,long options);
+long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+long mtx_parse (MAILSTREAM *stream);
+MESSAGECACHE *mtx_elt (MAILSTREAM *stream,unsigned long msgno);
+void mtx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
+void mtx_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag);
+unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size);
+
+
+/* MTX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mtxdriver = {
+ "mtx", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_CRLF|DR_NOSTICKY,
+ (DRIVER *) NIL, /* next driver */
+ mtx_valid, /* mailbox is valid for us */
+ mtx_parameters, /* manipulate parameters */
+ mtx_scan, /* scan mailboxes */
+ mtx_list, /* list mailboxes */
+ mtx_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ mtx_create, /* create mailbox */
+ mtx_delete, /* delete mailbox */
+ mtx_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ mtx_open, /* open mailbox */
+ mtx_close, /* close mailbox */
+ mtx_flags, /* fetch message "fast" attributes */
+ mtx_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mtx_header, /* fetch message header */
+ mtx_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ mtx_flag, /* modify flags */
+ mtx_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mtx_ping, /* ping mailbox to see if still alive */
+ mtx_check, /* check for new messages */
+ mtx_expunge, /* expunge deleted messages */
+ mtx_copy, /* copy messages to another mailbox */
+ mtx_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mtxproto = {&mtxdriver};
+
+/* MTX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mtx_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return mtx_isvalid (name,tmp) ? &mtxdriver : NIL;
+}
+
+
+/* MTX mail test for valid mailbox
+ * Accepts: mailbox name
+ * buffer to return file name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int mtx_isvalid (char *name,char *file)
+{
+ int fd;
+ int ret = NIL;
+ char *s,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ struct utimbuf times;
+ errno = EINVAL; /* assume invalid argument */
+ /* if file, get its status */
+ if ((s = dummy_file (file,name)) && !stat (s,&sbuf) &&
+ ((sbuf.st_mode & S_IFMT) == S_IFREG)) {
+ if (!sbuf.st_size)errno = 0;/* empty file */
+ else if ((fd = open (file,O_BINARY|O_RDONLY,NIL)) >= 0) {
+ memset (tmp,'\0',MAILTMPLEN);
+ if ((read (fd,tmp,64) >= 0) && (s = strchr (tmp,'\015')) &&
+ (s[1] == '\012')) { /* valid format? */
+ *s = '\0'; /* tie off header */
+ /* must begin with dd-mmm-yy" */
+ ret = (((tmp[2] == '-' && tmp[6] == '-') ||
+ (tmp[1] == '-' && tmp[5] == '-')) &&
+ (s = strchr (tmp+18,',')) && strchr (s+2,';')) ? T : NIL;
+ }
+ else errno = -1; /* bogus format */
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if (sbuf.st_ctime > sbuf.st_atime) {
+ /* preserve atime and mtime */
+ times.actime = sbuf.st_atime;
+ times.modtime = sbuf.st_mtime;
+ utime (file,&times); /* set the times */
+ }
+ }
+ }
+ /* in case INBOX but not mtx format */
+ else if ((errno == ENOENT) && !compare_cstring (name,"INBOX")) errno = -1;
+ return ret; /* return what we should */
+}
+
+
+/* MTX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mtx_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* MTX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MTX mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mtx_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MTX mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MTX mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,mbx[MAILTMPLEN];
+ if (s = dummy_file (mbx,mailbox)) return dummy_create (stream,s);
+ sprintf (mbx,"Can't create %.80s: invalid name",mailbox);
+ mm_log (mbx,ERROR);
+ return NIL;
+}
+
+
+/* MTX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return mtx_rename (stream,mailbox,NIL);
+}
+
+/* MTX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = LONGT;
+ char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ if (!dummy_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'\\')) && !s[1])))) {
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ if ((fd = open (file,O_BINARY|O_RDWR,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get exclusive parse/append permission */
+ if ((ld = lockname (lock,file,LOCK_EX)) < 0) {
+ mm_log ("Unable to lock rename mailbox",ERROR);
+ return NIL;
+ }
+ /* lock out other users */
+ if (flock (fd,LOCK_EX|LOCK_NB)) {
+ close (fd); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ mm_log (tmp,ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return NIL;
+ }
+
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if ((s = strrchr (tmp,'\\')) && (s != tmp) &&
+ ((tmp[1] != ':') || (s != tmp + 2))) {
+ c = s[1]; /* remember character after delimiter */
+ *s = s[1] = '\0'; /* tie off name at delimiter */
+ /* name doesn't exist, create it */
+ if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
+ *s = '\\'; /* restore delimiter */
+ if (!dummy_create (stream,tmp)) ret = NIL;
+ }
+ else *s = '\\'; /* restore delimiter */
+ s[1] = c; /* restore character after delimiter */
+ }
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* pacify NTFS */
+ /* rename the file */
+ if (ret && rename (file,tmp)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ else {
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* pacify NTFS */
+ if (unlink (file)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %.80s",old,strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return ret; /* return success */
+}
+
+/* MTX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mtx_open (MAILSTREAM *stream)
+{
+ int fd,ld;
+ char tmp[MAILTMPLEN];
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &mtxproto;
+ if (stream->local) fatal ("mtx recycle stream");
+ /* canonicalize the mailbox name */
+ if (!dummy_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ }
+ if (stream->rdonly ||
+ (fd = open (tmp,O_BINARY|O_RDWR,NIL)) < 0) {
+ if ((fd = open (tmp,O_BINARY|O_RDONLY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox: %.80s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ else if (!stream->rdonly) { /* got it, but readonly */
+ mm_log ("Can't get write access to mailbox, access is readonly",WARN);
+ stream->rdonly = T;
+ }
+ }
+ stream->local = fs_get (sizeof (MTXLOCAL));
+ LOCAL->fd = fd; /* bind the file */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ /* get shared parse permission */
+ if ((ld = lockname (tmp,stream->mailbox,LOCK_SH)) < 0) {
+ mm_log ("Unable to lock open mailbox",ERROR);
+ return NIL;
+ }
+ flock (LOCAL->fd,LOCK_SH); /* lock the file */
+ unlockfd (ld,tmp); /* release shared parse permission */
+ LOCAL->filesize = 0; /* initialize parsed file size */
+ LOCAL->filetime = 0; /* time not set up yet */
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ stream->sequence++; /* bump sequence number */
+ stream->uid_validity = (unsigned long) time (0);
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (mtx_ping (stream) && !stream->nmsgs)
+ mm_log ("Mailbox is empty",(long) NIL);
+ if (!LOCAL) return NIL; /* failure if stream died */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft =
+ stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ return stream; /* return stream to caller */
+}
+
+/* MTX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mtx_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ if (options & CL_EXPUNGE) mtx_expunge (stream,NIL,NIL);
+ stream->silent = silent; /* restore previous status */
+ flock (LOCAL->fd,LOCK_UN); /* unlock local file */
+ close (LOCAL->fd); /* close the local file */
+ /* free local text buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+
+/* MTX mail fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ * Sniffs at file to see if some other process changed the flags
+ */
+
+void mtx_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ unsigned long i;
+ if (mtx_ping (stream) && /* ping mailbox, get new status for messages */
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if (mail_elt (stream,i)->sequence) mtx_elt (stream,i);
+}
+
+/* MTX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mtx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get to header position */
+ lseek (LOCAL->fd,mtx_hdrpos (stream,msgno,length),L_SET);
+ /* is buffer big enough? */
+ if (*length > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = *length) + 1);
+ }
+ LOCAL->buf[*length] = '\0'; /* tie off string */
+ /* slurp the data */
+ read (LOCAL->fd,LOCAL->buf,*length);
+ return LOCAL->buf;
+}
+
+/* MTX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: T, always
+ */
+
+long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ FDDATA d;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mtx_elt (stream,msgno); /* get message status */
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ elt->seen = T; /* mark message as seen */
+ /* recalculate status */
+ mtx_update_status (stream,msgno,NIL);
+ mm_flags (stream,msgno);
+ }
+ /* find header position */
+ i = mtx_hdrpos (stream,msgno,&j);
+ d.fd = LOCAL->fd; /* set up file descriptor */
+ d.pos = i + j;
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE;
+ INIT (bs,fd_string,&d,elt->rfc822_size - j);
+ return T; /* success */
+}
+
+/* MTX mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ */
+
+void mtx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ struct utimbuf times;
+ struct stat sbuf;
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* make sure read comes after all that */
+ utime (stream->mailbox,&times);
+ }
+}
+
+
+/* MTX mail per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ struct stat sbuf;
+ /* maybe need to do a checkpoint? */
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ LOCAL->filetime = 0; /* don't do this test for any other messages */
+ }
+ /* recalculate status */
+ mtx_update_status (stream,elt->msgno,NIL);
+}
+
+/* MTX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long mtx_ping (MAILSTREAM *stream)
+{
+ unsigned long i = 1;
+ long r = T;
+ int ld;
+ char lock[MAILTMPLEN];
+ struct stat sbuf;
+ if (stream && LOCAL) { /* only if stream already open */
+ fstat (LOCAL->fd,&sbuf); /* get current file poop */
+ if (LOCAL->filetime && !(LOCAL->mustcheck || LOCAL->shouldcheck) &&
+ (LOCAL->filetime < sbuf.st_mtime)) LOCAL->shouldcheck = T;
+ /* check for changed message status */
+ if (LOCAL->mustcheck || LOCAL->shouldcheck) {
+ LOCAL->filetime = sbuf.st_mtime;
+ if (LOCAL->shouldcheck) /* babble when we do this unilaterally */
+ mm_notify (stream,"[CHECK] Checking for flag updates",NIL);
+ while (i <= stream->nmsgs) mtx_elt (stream,i++);
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ }
+ /* get shared parse/append permission */
+ if ((sbuf.st_size != LOCAL->filesize) &&
+ ((ld = lockname (lock,stream->mailbox,LOCK_SH)) >= 0)) {
+ /* parse resulting mailbox */
+ r = (mtx_parse (stream)) ? T : NIL;
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ }
+ return r; /* return result of the parse */
+}
+
+
+/* MTX mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void mtx_check (MAILSTREAM *stream)
+{
+ /* mark that a check is desired */
+ if (LOCAL) LOCAL->mustcheck = T;
+ if (mtx_ping (stream)) mm_log ("Check completed",(long) NIL);
+}
+
+/* MTX mail expunge mailbox
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long mtx_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ struct utimbuf times;
+ struct stat sbuf;
+ off_t pos = 0;
+ int ld;
+ unsigned long i = 1;
+ unsigned long j,k,m,recent;
+ unsigned long n = 0;
+ unsigned long delta = 0;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ if (!(ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ mtx_ping (stream))); /* parse sequence if given, ping stream */
+ else if (stream->rdonly) mm_log ("Expunge ignored on readonly mailbox",WARN);
+ else {
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ }
+ /* get exclusive parse/append permission */
+ if ((ld = lockname (lock,stream->mailbox,LOCK_EX)) < 0)
+ mm_log ("Unable to lock expunge mailbox",ERROR);
+ /* make sure see any newly-arrived messages */
+ else if (!mtx_parse (stream));
+ /* get exclusive access */
+ else if (flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
+ flock (LOCAL->fd,LOCK_SH);/* recover previous lock */
+ mm_log ("Can't expunge because mailbox is in use by another process",
+ ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+
+ else {
+ mm_critical (stream); /* go critical */
+ recent = stream->recent; /* get recent now that pinged and locked */
+ /* for each message */
+ while (i <= stream->nmsgs) {
+ /* get cache element */
+ elt = mtx_elt (stream,i);
+ /* number of bytes to smash or preserve */
+ k = elt->private.special.text.size + elt->rfc822_size;
+ /* if need to expunge this message */
+ if (elt->deleted && (sequence ? elt->sequence : T)) {
+ /* if recent, note one less recent message */
+ if (elt->recent) --recent;
+ delta += k; /* number of bytes to delete */
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else if (i++ && delta) {/* preserved message */
+ /* first byte to preserve */
+ j = elt->private.special.offset;
+ do { /* read from source position */
+ m = min (k,LOCAL->buflen);
+ lseek (LOCAL->fd,j,L_SET);
+ read (LOCAL->fd,LOCAL->buf,m);
+ pos = j - delta; /* write to destination position */
+ while (T) {
+ lseek (LOCAL->fd,pos,L_SET);
+ if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+ pos += m; /* new position */
+ j += m; /* next chunk, perhaps */
+ } while (k -= m); /* until done */
+ /* note the new address of this text */
+ elt->private.special.offset -= delta;
+ }
+ /* preserved but no deleted messages */
+ else pos = elt->private.special.offset + k;
+ }
+ if (n) { /* truncate file after last message */
+ if (pos != (LOCAL->filesize -= delta)) {
+ sprintf (LOCAL->buf,
+ "Calculated size mismatch %lu != %lu, delta = %lu",
+ (unsigned long) pos,(unsigned long) LOCAL->filesize,delta);
+ mm_log (LOCAL->buf,WARN);
+ LOCAL->filesize = pos;/* fix it then */
+ }
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ sprintf (LOCAL->buf,"Expunged %lu messages",n);
+ /* output the news */
+ mm_log (LOCAL->buf,(long) NIL);
+ }
+ else mm_log ("No messages deleted, so no update needed",(long) NIL);
+ fsync (LOCAL->fd); /* force disk update */
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* reset atime to now */
+ utime (stream->mailbox,&times);
+ mm_nocritical (stream); /* release critical */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ flock (LOCAL->fd,LOCK_SH);/* allow sharers again */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+ }
+ return ret;
+}
+
+/* MTX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ struct utimbuf times;
+ MESSAGECACHE *elt;
+ unsigned long i,j,k;
+ long ret = LONGT;
+ int fd,ld;
+ char file[MAILTMPLEN],lock[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ /* make sure valid mailbox */
+ if (!mtx_isvalid (mailbox,file)) switch (errno) {
+ case ENOENT: /* no such file? */
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid MTX-format mailbox name: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a MTX-format mailbox: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* got file? */
+ if ((fd = open (file,O_BINARY|O_RDWR|O_CREAT,S_IREAD|S_IWRITE)) < 0) {
+ sprintf (LOCAL->buf,"Unable to open copy mailbox: %.80s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ mm_critical (stream); /* go critical */
+ /* get exclusive parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockname (lock,file,LOCK_EX)) < 0)) {
+ mm_log ("Unable to lock copy mailbox",ERROR);
+ mm_nocritical (stream);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
+
+ /* for each requested message */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ /* number of bytes to copy */
+ k = elt->private.special.text.size + elt->rfc822_size;
+ do { /* read from source position */
+ j = min (k,LOCAL->buflen);
+ read (LOCAL->fd,LOCAL->buf,j);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ } while (ret && (k -= j));/* until done */
+ }
+ /* make sure all the updates take */
+ if (!(ret && (ret = !fsync (fd)))) {
+ sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ }
+ /* set atime to now-1 if successful copy */
+ if (ret) times.actime = time (0) - 1;
+ /* else preserved \Marked status */
+ else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
+ sbuf.st_atime : time (0);
+ times.modtime = sbuf.st_mtime;/* preserve mtime */
+ utime (file,&times); /* set the times */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ close (fd); /* close the file */
+ mm_nocritical (stream); /* release critical */
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE)) {
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mtx_elt (stream,i))->sequence) {
+ elt->deleted = T; /* mark message deleted */
+ /* recalculate status */
+ mtx_update_status (stream,i,NIL);
+ }
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* make sure atime remains greater */
+ utime (stream->mailbox,&times);
+ }
+ }
+ if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
+ mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN);
+ return ret;
+}
+
+/* MTX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd,ld,c;
+ char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ struct utimbuf times;
+ FILE *df;
+ MESSAGECACHE elt;
+ long f;
+ unsigned long i,uf;
+ STRING *message;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = &mtxproto;
+ /* make sure valid mailbox */
+ if (!mtx_isvalid (mailbox,file)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!compare_cstring (mailbox,"INBOX")) mtx_create (NIL,"INBOX");
+ else {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* falls through */
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid MTX-format mailbox name: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MTX-format mailbox: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get first message */
+ if (!(*af) (stream,data,&flags,&date,&message)) return NIL;
+
+ /* open destination mailbox */
+ if (((fd = open (file,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IREAD|S_IWRITE))
+ < 0) || !(df = fdopen (fd,"ab"))) {
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockname (lock,file,LOCK_EX)) < 0)) {
+ mm_log ("Unable to lock append mailbox",ERROR);
+ close (fd);
+ return NIL;
+ }
+ mm_critical (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ errno = 0;
+ do { /* parse flags */
+ if (!SIZE (message)) { /* guard against zero-length */
+ mm_log ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ f = mail_parse_flags (stream,flags,&i);
+ /* reverse bits (dontcha wish we had CIRC?) */
+ for (uf = 0; i; uf |= 1 << (29 - find_rightmost_bit (&i)));
+ if (date) { /* parse date if given */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ mm_log (tmp,ERROR);
+ ret = NIL; /* mark failure */
+ break;
+ }
+ mail_date (tmp,&elt); /* write preseved date */
+ }
+ else internal_date (tmp); /* get current date in IMAP format */
+ /* write header */
+ if (fprintf (df,"%s,%lu;%010lo%02lo\015\012",tmp,i = SIZE (message),uf,
+ (unsigned long) f) < 0) ret = NIL;
+ else { /* write message */
+ if (i) do c = 0xff & SNX (message);
+ while ((putc (c,df) != EOF) && --i);
+ /* get next message */
+ if (i || !(*af) (stream,data,&flags,&date,&message)) ret = NIL;
+ }
+ } while (ret && message);
+ /* if error... */
+ if (!ret || (fflush (df) == EOF)) {
+ ftruncate (fd,sbuf.st_size);/* revert file */
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ if (errno) {
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+ ret = NIL;
+ }
+ if (ret) times.actime = time (0) - 1;
+ /* else preserved \Marked status */
+ else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
+ sbuf.st_atime : time (0);
+ times.modtime = sbuf.st_mtime;/* preserve mtime */
+ utime (file,&times); /* set the times */
+ fclose (df); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ mm_nocritical (stream); /* release critical */
+ if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
+ mm_log ("Can not return meaningful APPENDUID with this mailbox format",
+ WARN);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* MTX mail parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long mtx_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = NIL;
+ unsigned char c,*s,*t,*x;
+ char tmp[MAILTMPLEN];
+ unsigned long i,j;
+ long curpos = LOCAL->filesize;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ short added = NIL;
+ short silent = stream->silent;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %ld to %ld!",curpos,sbuf.st_size);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ stream->silent = T; /* don't pass up mm_exists() events yet */
+ while (sbuf.st_size - curpos){/* while there is stuff to parse */
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,L_SET);
+ if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
+ sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size,
+ i ? strerror (errno) : "no data read");
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
+ if (!((s = strchr (LOCAL->buf,'\015')) && (s[1] == '\012'))) {
+ sprintf (tmp,"Unable to find CRLF at %lu in %lu bytes, text: %s",
+ (unsigned long) curpos,i,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ *s = '\0'; /* tie off header line */
+ i = (s + 2) - LOCAL->buf; /* note start of text offset */
+ if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
+ sprintf (tmp,"Unable to parse internal header at %lu: %s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ *s++ = '\0'; *t++ = '\0'; /* tie off fields */
+
+ added = T; /* note that a new message was added */
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ /* instantiate an elt for this message */
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ elt->private.uid = ++stream->uid_last;
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* in case error */
+ elt->private.special.text.size = 0;
+ /* header size not known yet */
+ elt->private.msg.header.text.size = 0;
+ x = s; /* parse the header components */
+ if (mail_parse_date (elt,LOCAL->buf) &&
+ (elt->rfc822_size = strtoul (s,(char **) &s,10)) && (!(s && *s)) &&
+ isdigit (t[0]) && isdigit (t[1]) && isdigit (t[2]) &&
+ isdigit (t[3]) && isdigit (t[4]) && isdigit (t[5]) &&
+ isdigit (t[6]) && isdigit (t[7]) && isdigit (t[8]) &&
+ isdigit (t[9]) && isdigit (t[10]) && isdigit (t[11]) && !t[12])
+ elt->private.special.text.size = i;
+ else { /* oops */
+ sprintf (tmp,"Unable to parse internal header elements at %ld: %s,%s;%s",
+ curpos,(char *) LOCAL->buf,(char *) x,(char *) t);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ /* make sure didn't run off end of file */
+ if ((curpos += (elt->rfc822_size + i)) > sbuf.st_size) {
+ sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
+ elt->private.special.offset,(unsigned long) curpos,
+ (unsigned long) sbuf.st_size);
+ mm_log (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ c = t[10]; /* remember first system flags byte */
+ t[10] = '\0'; /* tie off flags */
+ j = strtoul (t,NIL,8); /* get user flags value */
+ t[10] = c; /* restore first system flags byte */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ /* calculate system flags */
+ if ((j = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T;
+ if (j & fDELETED) elt->deleted = T;
+ if (j & fFLAGGED) elt->flagged = T;
+ if (j & fANSWERED) elt->answered = T;
+ if (j & fDRAFT) elt->draft = T;
+ if (!(j & fOLD)) { /* newly arrived message? */
+ elt->recent = T;
+ recent++; /* count up a new recent message */
+ /* mark it as old */
+ mtx_update_status (stream,nmsgs,NIL);
+ }
+ }
+ fsync (LOCAL->fd); /* make sure all the fOLD flags take */
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
+ LOCAL->filetime = sbuf.st_mtime;
+ if (added && !stream->rdonly){/* make sure atime updated */
+ struct utimbuf times;
+ times.actime = time (0);
+ times.modtime = LOCAL->filetime;
+ utime (stream->mailbox,&times);
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return LONGT; /* return the winnage */
+}
+
+/* MTX get cache element with status updating from file
+ * Accepts: MAIL stream
+ * message number
+ * Returns: cache element
+ */
+
+MESSAGECACHE *mtx_elt (MAILSTREAM *stream,unsigned long msgno)
+{
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
+ old.answered = elt->answered; old.draft = elt->draft;
+ old.user_flags = elt->user_flags;
+ mtx_read_flags (stream,elt);
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
+ (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
+ mm_flags (stream,msgno); /* let top level know */
+ return elt;
+}
+
+/* MTX read flags from file
+ * Accepts: MAIL stream
+ * Returns: cache element
+ */
+
+void mtx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ unsigned long i,j;
+ /* noop if readonly and have valid flags */
+ if (stream->rdonly && elt->valid) return;
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 14,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,12) < 0) {
+ sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ /* calculate system flags */
+ i = (((LOCAL->buf[10]-'0') * 8) + LOCAL->buf[11]-'0');
+ elt->seen = i & fSEEN ? T : NIL; elt->deleted = i & fDELETED ? T : NIL;
+ elt->flagged = i & fFLAGGED ? T : NIL;
+ elt->answered = i & fANSWERED ? T : NIL; elt->draft = i & fDRAFT ? T : NIL;
+ LOCAL->buf[10] = '\0'; /* tie off flags */
+ j = strtoul(LOCAL->buf,NIL,8);/* get user flags value */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ elt->valid = T; /* have valid flags now */
+}
+
+/* MTX update status string
+ * Accepts: MAIL stream
+ * message number
+ * flag saying whether or not to sync
+ */
+
+void mtx_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag)
+{
+ struct utimbuf times;
+ struct stat sbuf;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ unsigned long j,k = 0;
+ /* readonly */
+ if (stream->rdonly || !elt->valid) mtx_read_flags (stream,elt);
+ else { /* readwrite */
+ j = elt->user_flags; /* get user flags */
+ /* reverse bits (dontcha wish we had CIRC?) */
+ while (j) k |= 1 << (29 - find_rightmost_bit (&j));
+ /* print new flag string */
+ sprintf (LOCAL->buf,"%010lo%02o",k,(unsigned)
+ (fOLD + (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ while (T) { /* get to that place in the file */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 14,L_SET);
+ /* write new flags */
+ if (write (LOCAL->fd,LOCAL->buf,12) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+ if (syncflag) { /* sync if requested */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* make sure read is later */
+ utime (stream->mailbox,&times);
+ }
+ }
+}
+
+/* MTX locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * Returns: position of header in file
+ */
+
+unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size)
+{
+ unsigned long siz;
+ long i = 0;
+ int q = 0;
+ char *s,tmp[MAILTMPLEN];
+ MESSAGECACHE *elt = mtx_elt (stream,msgno);
+ unsigned long ret = elt->private.special.offset +
+ elt->private.special.text.size;
+ /* is header size known? */
+ if (!(*size = elt->private.msg.header.text.size)) {
+ lseek (LOCAL->fd,ret,L_SET);/* get to header position */
+ /* search message for CRLF CRLF */
+ for (siz = 1,s = tmp; siz <= elt->rfc822_size; siz++) {
+ /* read another buffer as necessary */
+ if ((--i <= 0) && /* buffer empty? */
+ (read (LOCAL->fd,s = tmp,
+ i = min (elt->rfc822_size - siz,(long) MAILTMPLEN)) < 0))
+ return ret; /* I/O error? */
+ switch (q) { /* sniff at buffer */
+ case 0: /* first character */
+ q = (*s++ == '\015') ? 1 : 0;
+ break;
+ case 1: /* second character */
+ q = (*s++ == '\012') ? 2 : 0;
+ break;
+ case 2: /* third character */
+ q = (*s++ == '\015') ? 3 : 0;
+ break;
+ case 3: /* fourth character */
+ if (*s++ == '\012') { /* have the sequence? */
+ /* yes, note for later */
+ elt->private.msg.header.text.size = *size = siz;
+ return ret;
+ }
+ q = 0; /* lost... */
+ break;
+ }
+ }
+ /* header consumes entire message */
+ elt->private.msg.header.text.size = *size = elt->rfc822_size;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/os2/nl_os2.c b/imap/src/osdep/os2/nl_os2.c
new file mode 100644
index 00000000..47cb7f0a
--- /dev/null
+++ b/imap/src/osdep/os2/nl_os2.c
@@ -0,0 +1,61 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Windows/TOPS-20 newline routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Copy string with CRLF newlines
+ * Accepts: destination string
+ * pointer to size of destination string buffer
+ * source string
+ * length of source string
+ * Returns: length of copied string
+ */
+
+unsigned long strcrlfcpy (unsigned char **dst,unsigned long *dstl,
+ unsigned char *src,unsigned long srcl)
+{
+ /* flush destination buffer if too small */
+ if (*dst && (srcl > *dstl)) fs_give ((void **) dst);
+ if (!*dst) { /* make a new buffer if needed */
+ *dst = (char *) fs_get ((size_t) (*dstl = srcl) + 1);
+ if (dstl) *dstl = srcl; /* return new buffer length to main program */
+ }
+ /* copy strings */
+ if (srcl) memcpy (*dst,src,(size_t) srcl);
+ *(*dst + srcl) = '\0'; /* tie off destination */
+ return srcl; /* return length */
+}
+
+
+/* Length of string after strcrlfcpy applied
+ * Accepts: source string
+ * Returns: length of string
+ */
+
+unsigned long strcrlflen (STRING *s)
+{
+ return SIZE (s); /* no-brainer on DOS! */
+}
diff --git a/imap/src/osdep/os2/os_os2.c b/imap/src/osdep/os2/os_os2.c
new file mode 100644
index 00000000..9c6f2f5f
--- /dev/null
+++ b/imap/src/osdep/os2/os_os2.c
@@ -0,0 +1,100 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- OS/2 emx version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 14 March 1996
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdio.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "tcp_os2.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include "misc.h"
+#include "fs_os2.c"
+#include "ftl_os2.c"
+#include "nl_os2.c"
+#include "env_os2.c"
+#include "tcp_os2.c"
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost (void)
+{
+ if (!myLocalHost) { /* known yet? */
+ char *s,tmp[MAILTMPLEN];
+ struct hostent *he;
+ /* could we get local id? */
+ gethostname (tmp,MAILTMPLEN-1);
+ if (he = gethostbyname (lcase (tmp))) {
+ if (he->h_name) s = he->h_name;
+ else sprintf (s = tmp,"[%i.%i.%i.%i]",he->h_addr[0],he->h_addr[1],
+ he->h_addr[2],he->h_addr[3]);
+ }
+ else s = "random-pc";
+ myLocalHost = cpystr (s); /* record for subsequent use */
+ }
+ return myLocalHost;
+}
+
+
+/* Look up host address
+ * Accepts: pointer to pointer to host name
+ * socket address block
+ * Returns: non-zero with host address in socket, official host name in host;
+ * else NIL
+ */
+
+long lookuphost (char **host,struct sockaddr_in *sin)
+{
+ long ret = -1;
+ char tmp[MAILTMPLEN];
+ struct hostent *hn = gethostbyname (lcase (strcpy (tmp,*host)));
+ if (!hn) return NIL; /* got a host name? */
+ *host = cpystr (hn->h_name); /* set official name */
+ /* copy host addresses */
+ memcpy (&sin->sin_addr,hn->h_addr,hn->h_length);
+ return T;
+}
+
+
+/*
+ * Emulator for BSD syslog() routine.
+ */
+
+void syslog (int priority,const char *message,...)
+{
+}
diff --git a/imap/src/osdep/os2/os_os2.h b/imap/src/osdep/os2/os_os2.h
new file mode 100644
index 00000000..0dd0a62b
--- /dev/null
+++ b/imap/src/osdep/os2/os_os2.h
@@ -0,0 +1,38 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- OS/2 emx version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 14 March 1996
+ * Last Edited: 30 August 2006
+ */
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include "env_os2.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/os2/pmatch.c b/imap/src/osdep/os2/pmatch.c
new file mode 100644
index 00000000..95a0bb86
--- /dev/null
+++ b/imap/src/osdep/os2/pmatch.c
@@ -0,0 +1,89 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: IMAP Wildcard Matching Routines (case-independent)
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 June 2000
+ * Last Edited: 30 August 2006
+ */
+
+/* Wildcard pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if pattern matches base, else NIL
+ */
+
+long pmatch_full (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ /* % at end, OK if no inferiors */
+ if (!pat[1]) return (delim && strchr (s,delim)) ? NIL : T;
+ /* scan remainder of string until delimiter */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while ((*s != delim) && *s++);
+ break;
+ case '*': /* match 0 or more characters */
+ if (!pat[1]) return T; /* * at end, unconditional match */
+ /* scan remainder of string */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while (*s++);
+ break;
+ case '\0': /* end of pattern */
+ return *s ? NIL : T; /* success if also end of base */
+ default: /* match this character */
+ return compare_uchar (*pat,*s) ? NIL : pmatch_full (s+1,pat+1,delim);
+ }
+ return NIL;
+}
+
+/* Directory pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if base is a matching directory of pattern, else NIL
+ */
+
+long dmatch (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ if (!*s) return T; /* end of base means have a subset match */
+ if (!*++pat) return NIL; /* % at end, no inferiors permitted */
+ /* scan remainder of string until delimiter */
+ do if (dmatch (s,pat,delim)) return T;
+ while ((*s != delim) && *s++);
+ if (*s && !s[1]) return T; /* ends with delimiter, must be subset */
+ return dmatch (s,pat,delim);/* do new scan */
+ case '*': /* match 0 or more characters */
+ return T; /* unconditional match */
+ case '\0': /* end of pattern */
+ break;
+ default: /* match this character */
+ if (*s) return compare_uchar (*pat,*s) ? NIL : dmatch (s+1,pat+1,delim);
+ /* end of base, return if at delimiter */
+ else if (*pat == delim) return T;
+ break;
+ }
+ return NIL;
+}
diff --git a/imap/src/osdep/os2/pseudo.c b/imap/src/osdep/os2/pseudo.c
new file mode 100644
index 00000000..1aae8a53
--- /dev/null
+++ b/imap/src/osdep/os2/pseudo.c
@@ -0,0 +1,36 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Pseudo Header Strings
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 September 1996
+ * Last Edited: 30 August 2006
+ */
+
+/* Local sites may wish to alter this text */
+
+char *pseudo_from = "MAILER-DAEMON";
+char *pseudo_name = "Mail System Internal Data";
+char *pseudo_subject = "DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA";
+char *pseudo_msg =
+ "This text is part of the internal format of your mail folder, and is not\na real message. It is created automatically by the mail system software.\nIf deleted, important folder data will be lost, and it will be re-created\nwith the data reset to initial values."
+ ;
diff --git a/imap/src/osdep/os2/pseudo.h b/imap/src/osdep/os2/pseudo.h
new file mode 100644
index 00000000..c9c07628
--- /dev/null
+++ b/imap/src/osdep/os2/pseudo.h
@@ -0,0 +1,30 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Pseudo Header Strings
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 September 1996
+ * Last Edited: 30 August 2006
+ */
+
+
+extern char *pseudo_from,*pseudo_name,*pseudo_subject,*pseudo_msg;
diff --git a/imap/src/osdep/os2/setproto.cmd b/imap/src/osdep/os2/setproto.cmd
new file mode 100644
index 00000000..5b9d6eef
--- /dev/null
+++ b/imap/src/osdep/os2/setproto.cmd
@@ -0,0 +1,34 @@
+/* rexx */
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+/*
+ * Program: Set default prototype for OS/2
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 June 1999
+ * Last Edited: 30 August 2006
+ */
+'@echo off'
+parse arg default args
+h_file='linkage.h'
+call stream h_file, 'C', 'open write'
+call lineout h_file, '#define DEFAULTPROTO' default'proto'
+call stream h_file, 'C', 'close'
+exit 0
diff --git a/imap/src/osdep/os2/tcp_os2.c b/imap/src/osdep/os2/tcp_os2.c
new file mode 100644
index 00000000..062f260c
--- /dev/null
+++ b/imap/src/osdep/os2/tcp_os2.c
@@ -0,0 +1,434 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MS-DOS TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 13 January 2008
+ */
+
+static tcptimeout_t tmoh = NIL; /* TCP timeout handler routine */
+static long ttmo_read = 0; /* TCP timeouts, in seconds */
+static long ttmo_write = 0;
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd);
+
+/* TCP/IP manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tcp_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_TIMEOUT:
+ tmoh = (tcptimeout_t) value;
+ case GET_TIMEOUT:
+ ret = (void *) tmoh;
+ break;
+ case SET_READTIMEOUT:
+ ttmo_read = (long) value;
+ case GET_READTIMEOUT:
+ ret = (void *) ttmo_read;
+ break;
+ case SET_WRITETIMEOUT:
+ ttmo_write = (long) value;
+ case GET_WRITETIMEOUT:
+ ret = (void *) ttmo_write;
+ break;
+ }
+ return ret;
+}
+
+/* TCP/IP open
+ * Accepts: host name
+ * contact service name
+ * contact port number
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = NIL;
+ struct sockaddr_in sin;
+ int sock;
+ char *s,tmp[MAILTMPLEN];
+ port &= 0xffff; /* erase flags */
+ /* The domain literal form is used (rather than simply the dotted decimal
+ as with other Unix programs) because it has to be a valid "host name"
+ in mailsystem terminology. */
+ sin.sin_family = AF_INET; /* family is always Internet */
+ /* look like domain literal? */
+ if (host[0] == '[' && host[(strlen (host))-1] == ']') {
+ strcpy (tmp,host+1); /* yes, copy number part */
+ tmp[strlen (tmp)-1] = '\0';
+ if ((sin.sin_addr.s_addr = inet_addr (tmp)) == -1) {
+ sprintf (tmp,"Bad format domain-literal: %.80s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ }
+ /* look up host name */
+ else if (!lookuphost (&host,&sin)) {
+ sprintf (tmp,"Host not found: %s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+
+ /* copy port number in network format */
+ if (!(sin.sin_port = htons (port))) fatal ("Bad port argument to tcp_open");
+ /* get a TCP stream */
+ if ((sock = socket (sin.sin_family,SOCK_STREAM,0)) < 0) {
+ sprintf (tmp,"Unable to create TCP socket (%d)",errno);
+ mm_log (tmp,ERROR);
+ fs_give ((void **) &host);
+ return NIL;
+ }
+#if 0
+ /* needed? */
+ else if (sock >= FD_SETSIZE) {/* unselectable sockets are useless */
+ sprintf (tmp,"Unable to create selectable TCP socket (%d >= %d)",
+ sock,FD_SETSIZE);
+ close (sock);
+ errno = ENOBUFS; /* just in case */
+ return NIL;
+ }
+#endif
+ /* open connection */
+ if (connect (sock,(struct sockaddr *) &sin,sizeof (sin)) < 0) {
+ switch (errno) { /* analyze error */
+ case ECONNREFUSED:
+ s = "Refused";
+ break;
+ case ENOBUFS:
+ s = "Insufficient system resources";
+ break;
+ case ETIMEDOUT:
+ s = "Timed out";
+ break;
+ default:
+ s = "Unknown error";
+ break;
+ }
+ sprintf (tmp,"Can't connect to %.80s,%ld: %s (%d)",host,port,s,errno);
+ mm_log (tmp,ERROR);
+ close (sock);
+ fs_give ((void **) &host);
+ return NIL;
+ }
+ /* create TCP/IP stream */
+ stream = (TCPSTREAM *) fs_get (sizeof (TCPSTREAM));
+ stream->host = host; /* official host name */
+ stream->localhost = cpystr (mylocalhost ());
+ stream->port = port; /* port number */
+ stream->tcps = sock; /* init socket */
+ stream->ictr = 0; /* init input counter */
+ return stream; /* return success */
+}
+
+/* TCP/IP authenticated open
+ * Accepts: NETMBX specifier
+ * service name
+ * returned user name buffer
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL; /* always NIL on DOS */
+}
+
+/* TCP receive line
+ * Accepts: TCP stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *tcp_getline (TCPSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = tcp_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = tcp_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* TCP receive line or partial line
+ * Accepts: TCP stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!tcp_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!tcp_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* TCP/IP receive buffer
+ * Accepts: TCP/IP stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *buffer)
+{
+ unsigned long n;
+ char *bufptr = buffer;
+ while (size > 0) { /* until request satisfied */
+ if (!tcp_getdata (stream)) return NIL;
+ n = min (size,stream->ictr);/* number of bytes to transfer */
+ /* do the copy */
+ memcpy (bufptr,stream->iptr,n);
+ bufptr += n; /* update pointer */
+ stream->iptr +=n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -=n;
+ }
+ bufptr[0] = '\0'; /* tie off string */
+ return T;
+}
+
+/* TCP/IP receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getdata (TCPSTREAM *stream)
+{
+ int i;
+ fd_set fds,efds;
+ struct timeval tmo;
+ time_t t = time (0);
+ if (stream->tcps < 0) return NIL;
+ while (stream->ictr < 1) { /* if nothing in the buffer */
+ time_t tl = time (0); /* start of request */
+ tmo.tv_sec = ttmo_read; /* read timeout */
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ FD_SET (stream->tcps,&fds);/* set bit in selection vector */
+ FD_SET(stream->tcps,&efds);/* set bit in error selection vector */
+ errno = NIL; /* block and read */
+ while (((i = select (stream->tcps+1,&fds,0,&efds,ttmo_read ? &tmo : 0))<0)
+ && (errno == EINTR));
+ if (!i) { /* timeout? */
+ time_t tc = time (0);
+ if (tmoh && ((*tmoh) (tc - t,tc - tl))) continue;
+ else return tcp_abort (stream);
+ }
+ else if (i < 0) return tcp_abort (stream);
+ while (((i = read (stream->tcps,stream->ibuf,BUFLEN)) < 0) &&
+ (errno == EINTR));
+ if (i < 1) return tcp_abort (stream);
+ stream->iptr = stream->ibuf;/* point at TCP buffer */
+ stream->ictr = i; /* set new byte count */
+ }
+ return T;
+}
+
+/* TCP/IP send string as record
+ * Accepts: TCP/IP stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long tcp_soutr (TCPSTREAM *stream,char *string)
+{
+ return tcp_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* TCP/IP send string
+ * Accepts: TCP/IP stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
+{
+ int i;
+ fd_set fds;
+ struct timeval tmo;
+ time_t t = time (0);
+ if (stream->tcps < 0) return NIL;
+ while (size > 0) { /* until request satisfied */
+ time_t tl = time (0); /* start of request */
+ tmo.tv_sec = ttmo_write; /* write timeout */
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_SET (stream->tcps,&fds);/* set bit in selection vector */
+ errno = NIL; /* block and write */
+ while (((i = select (stream->tcps+1,0,&fds,0,ttmo_write ? &tmo : 0)) < 0)
+ && (errno == EINTR));
+ if (!i) { /* timeout? */
+ time_t tc = time (0);
+ if (tmoh && ((*tmoh) (tc - t,tc - tl))) continue;
+ else return tcp_abort (stream);
+ }
+ else if (i < 0) return tcp_abort (stream);
+ while (((i = write (stream->tcps,string,size)) < 0) && (errno == EINTR));
+ if (i < 0) return tcp_abort (stream);
+ size -= i; /* how much we sent */
+ string += i;
+ }
+ return T; /* all done */
+}
+
+/* TCP/IP close
+ * Accepts: TCP/IP stream
+ */
+
+void tcp_close (TCPSTREAM *stream)
+{
+ tcp_abort (stream); /* nuke the socket */
+ /* flush host names */
+ fs_give ((void **) &stream->host);
+ fs_give ((void **) &stream->localhost);
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* TCP/IP abort stream
+ * Accepts: TCP/IP stream
+ * Returns: NIL always
+ */
+
+long tcp_abort (TCPSTREAM *stream)
+{
+ if (stream->tcps >= 0) close (stream->tcps);
+ stream->tcps = -1;
+ return NIL;
+}
+
+/* TCP/IP get host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_host (TCPSTREAM *stream)
+{
+ return stream->host; /* return host name */
+}
+
+
+/* TCP/IP get remote host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_remotehost (TCPSTREAM *stream)
+{
+ return stream->host; /* all we can do for now */
+}
+
+
+/* TCP/IP return port for this stream
+ * Accepts: TCP/IP stream
+ * Returns: port number for this stream
+ */
+
+unsigned long tcp_port (TCPSTREAM *stream)
+{
+ return stream->port; /* return port number */
+}
+
+
+/* TCP/IP get local host name
+ * Accepts: TCP/IP stream
+ * Returns: local host name
+ */
+
+char *tcp_localhost (TCPSTREAM *stream)
+{
+ return stream->localhost; /* return local host name */
+}
+
+
+/* TCP/IP return canonical form of host name
+ * Accepts: host name
+ * Returns: canonical form of host name
+ */
+
+char *tcp_canonical (char *name)
+{
+ return name;
+}
+
+
+/* TCP/IP get client host name (server calls only)
+ * Returns: client host name
+ */
+
+char *tcp_clienthost ()
+{
+ return "UNKNOWN";
+}
diff --git a/imap/src/osdep/os2/tcp_os2.h b/imap/src/osdep/os2/tcp_os2.h
new file mode 100644
index 00000000..2933a5ff
--- /dev/null
+++ b/imap/src/osdep/os2/tcp_os2.h
@@ -0,0 +1,51 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: OS2 routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+/* TCP input buffer -- must be large enough to prevent overflow */
+
+#define BUFLEN 8192
+
+
+/* TCP I/O stream (must be before osdep.h is included) */
+
+#define TCPSTREAM struct tcp_stream
+TCPSTREAM {
+ char *host; /* host name */
+ unsigned long port; /* port number */
+ char *localhost; /* local host name */
+ int tcps; /* tcp socket */
+ long ictr; /* input counter */
+ char *iptr; /* input pointer */
+ char ibuf[BUFLEN]; /* input buffer */
+};
+
+
+/* Local function prototypes */
+
+long lookuphost (char **host,struct sockaddr_in *sin);
+long tcp_abort (TCPSTREAM *stream);
diff --git a/imap/src/osdep/os2/tenexnt.c b/imap/src/osdep/os2/tenexnt.c
new file mode 100644
index 00000000..4805b7c5
--- /dev/null
+++ b/imap/src/osdep/os2/tenexnt.c
@@ -0,0 +1,1310 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Tenex mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 22 May 1990
+ * Last Edited: 18 June 2007
+ */
+
+
+/* FILE TIME SEMANTICS
+ *
+ * The atime is the last read time of the file.
+ * The mtime is the last flags update time of the file.
+ * The ctime is the last write time of the file.
+ *
+ * TEXT SIZE SEMANTICS
+ *
+ * Most of the text sizes are in internal (LF-only) form, except for the
+ * msg.text size. Beware.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/utime.h>
+#include "misc.h"
+#include "dummy.h"
+
+/* TENEX I/O stream local data */
+
+typedef struct tenex_local {
+ unsigned int shouldcheck: 1; /* if ping should do a check instead */
+ unsigned int mustcheck: 1; /* if ping must do a check instead */
+ int fd; /* file descriptor for I/O */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* local snarf time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned long uid; /* current text uid */
+ SIZEDTEXT text; /* current text */
+} TENEXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((TENEXLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *tenex_valid (char *name);
+int tenex_isvalid (char *name,char *file);
+void *tenex_parameters (long function,void *value);
+void tenex_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void tenex_list (MAILSTREAM *stream,char *ref,char *pat);
+void tenex_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long tenex_create (MAILSTREAM *stream,char *mailbox);
+long tenex_delete (MAILSTREAM *stream,char *mailbox);
+long tenex_rename (MAILSTREAM *stream,char *old,char *newname);
+long tenex_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *tenex_open (MAILSTREAM *stream);
+void tenex_close (MAILSTREAM *stream,long options);
+void tenex_fast (MAILSTREAM *stream,char *sequence,long flags);
+void tenex_flags (MAILSTREAM *stream,char *sequence,long flags);
+char *tenex_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long tenex_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void tenex_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void tenex_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long tenex_ping (MAILSTREAM *stream);
+void tenex_check (MAILSTREAM *stream);
+void tenex_snarf (MAILSTREAM *stream);
+long tenex_expunge (MAILSTREAM *stream,char *sequence,long options);
+long tenex_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long tenex_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+unsigned long tenex_size (MAILSTREAM *stream,unsigned long m);
+long tenex_parse (MAILSTREAM *stream);
+MESSAGECACHE *tenex_elt (MAILSTREAM *stream,unsigned long msgno);
+void tenex_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
+void tenex_update_status (MAILSTREAM *stream,unsigned long msgno,
+ long syncflag);
+unsigned long tenex_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size);
+
+
+/* Tenex mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER tenexdriver = {
+ "tenex", /* driver name */
+ DR_LOCAL|DR_MAIL, /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ tenex_valid, /* mailbox is valid for us */
+ tenex_parameters, /* manipulate parameters */
+ tenex_scan, /* scan mailboxes */
+ tenex_list, /* list mailboxes */
+ tenex_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ tenex_create, /* create mailbox */
+ tenex_delete, /* delete mailbox */
+ tenex_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ tenex_open, /* open mailbox */
+ tenex_close, /* close mailbox */
+ tenex_flags, /* fetch message "fast" attributes */
+ tenex_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ tenex_header, /* fetch message header */
+ tenex_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ tenex_flag, /* modify flags */
+ tenex_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ tenex_ping, /* ping mailbox to see if still alive */
+ tenex_check, /* check for new messages */
+ tenex_expunge, /* expunge deleted messages */
+ tenex_copy, /* copy messages to another mailbox */
+ tenex_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM tenexproto = {&tenexdriver};
+
+/* Tenex mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *tenex_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return tenex_isvalid (name,tmp) ? &tenexdriver : NIL;
+}
+
+
+/* Tenex mail test for valid mailbox
+ * Accepts: mailbox name
+ * buffer to return file name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int tenex_isvalid (char *name,char *file)
+{
+ int fd;
+ int ret = NIL;
+ char *s,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ struct utimbuf times;
+ errno = EINVAL; /* assume invalid argument */
+ /* if file, get its status */
+ if ((s = dummy_file (file,name)) && !stat (s,&sbuf) &&
+ ((sbuf.st_mode & S_IFMT) == S_IFREG)) {
+ if (!sbuf.st_size)errno = 0;/* empty file */
+ else if ((fd = open (file,O_BINARY|O_RDONLY,NIL)) >= 0) {
+ memset (tmp,'\0',MAILTMPLEN);
+ if ((read (fd,tmp,64) >= 0) && (s = strchr (tmp,'\012')) &&
+ (s[-1] != '\015')) { /* valid format? */
+ *s = '\0'; /* tie off header */
+ /* must begin with dd-mmm-yy" */
+ ret = (((tmp[2] == '-' && tmp[6] == '-') ||
+ (tmp[1] == '-' && tmp[5] == '-')) &&
+ (s = strchr (tmp+18,',')) && strchr (s+2,';')) ? T : NIL;
+ }
+ else errno = -1; /* bogus format */
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if (sbuf.st_ctime > sbuf.st_atime) {
+ /* preserve atime and mtime */
+ times.actime = sbuf.st_atime;
+ times.modtime = sbuf.st_mtime;
+ utime (file,&times); /* set the times */
+ }
+ }
+ }
+ /* in case INBOX but not tenex format */
+ else if ((errno == ENOENT) && !compare_cstring (name,"INBOX")) errno = -1;
+ return ret; /* return what we should */
+}
+
+
+/* Tenex manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tenex_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* Tenex mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void tenex_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* Tenex mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void tenex_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* Tenex mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void tenex_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* Tenex mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long tenex_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,mbx[MAILTMPLEN];
+ if (s = dummy_file (mbx,mailbox)) return dummy_create (stream,s);
+ sprintf (mbx,"Can't create %.80s: invalid name",mailbox);
+ mm_log (mbx,ERROR);
+ return NIL;
+}
+
+
+/* Tenex mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long tenex_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return tenex_rename (stream,mailbox,NIL);
+}
+
+/* Tenex mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long tenex_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = LONGT;
+ char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ if (!dummy_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'\\')) && !s[1])))) {
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ else if ((fd = open (file,O_BINARY|O_RDWR,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get exclusive parse/append permission */
+ if ((ld = lockname (lock,file,LOCK_EX)) < 0) {
+ mm_log ("Unable to lock rename mailbox",ERROR);
+ return NIL;
+ }
+ /* lock out other users */
+ if (flock (fd,LOCK_EX|LOCK_NB)) {
+ close (fd); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ mm_log (tmp,ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return NIL;
+ }
+
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if ((s = strrchr (tmp,'\\')) && (s != tmp) &&
+ ((tmp[1] != ':') || (s != tmp + 2))) {
+ c = s[1]; /* remember character after delimiter */
+ *s = s[1] = '\0'; /* tie off name at delimiter */
+ /* name doesn't exist, create it */
+ if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
+ *s = '\\'; /* restore delimiter */
+ if (!dummy_create (stream,tmp)) ret = NIL;
+ }
+ else *s = '\\'; /* restore delimiter */
+ s[1] = c; /* restore character after delimiter */
+ }
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* pacify NTFS */
+ /* rename the file */
+ if (ret && rename (file,tmp)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ else {
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* pacify NTFS */
+ if (unlink (file)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %.80s",old,strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return ret; /* return success */
+}
+
+/* Tenex mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *tenex_open (MAILSTREAM *stream)
+{
+ int fd,ld;
+ char tmp[MAILTMPLEN];
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &tenexproto;
+ if (stream->local) fatal ("tenex recycle stream");
+ /* canonicalize the mailbox name */
+ if (!dummy_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ }
+ if (stream->rdonly ||
+ (fd = open (tmp,O_BINARY|O_RDWR,NIL)) < 0) {
+ if ((fd = open (tmp,O_BINARY|O_RDONLY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox: %.80s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ else if (!stream->rdonly) { /* got it, but readonly */
+ mm_log ("Can't get write access to mailbox, access is readonly",WARN);
+ stream->rdonly = T;
+ }
+ }
+ stream->local = fs_get (sizeof (TENEXLOCAL));
+ LOCAL->fd = fd; /* bind the file */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE);
+ LOCAL->text.size = CHUNKSIZE - 1;
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ /* get shared parse permission */
+ if ((ld = lockname (tmp,stream->mailbox,LOCK_SH)) < 0) {
+ mm_log ("Unable to lock open mailbox",ERROR);
+ return NIL;
+ }
+ flock (LOCAL->fd,LOCK_SH); /* lock the file */
+ unlockfd (ld,tmp); /* release shared parse permission */
+ LOCAL->filesize = 0; /* initialize parsed file size */
+ LOCAL->filetime = 0; /* time not set up yet */
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ stream->sequence++; /* bump sequence number */
+ stream->uid_validity = (unsigned long) time (0);
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (tenex_ping (stream) && !stream->nmsgs)
+ mm_log ("Mailbox is empty",(long) NIL);
+ if (!LOCAL) return NIL; /* failure if stream died */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft =
+ stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ return stream; /* return stream to caller */
+}
+
+/* Tenex mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void tenex_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ if (options & CL_EXPUNGE) tenex_expunge (stream,NIL,NIL);
+ stream->silent = silent; /* restore previous status */
+ flock (LOCAL->fd,LOCK_UN); /* unlock local file */
+ close (LOCAL->fd); /* close the local file */
+ /* free local text buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* Tenex mail fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ * Sniffs at file to get flags
+ */
+
+void tenex_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ STRING bs;
+ MESSAGECACHE *elt;
+ unsigned long i;
+ if (stream && LOCAL &&
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ if (!elt->rfc822_size) { /* have header size yet? */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.special.text.size,L_SET);
+ /* resize bigbuf if necessary */
+ if (LOCAL->buflen < elt->private.msg.full.text.size) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buflen = elt->private.msg.full.text.size;
+ LOCAL->buf = (char *) fs_get (LOCAL->buflen + 1);
+ }
+ /* tie off string */
+ LOCAL->buf[elt->private.msg.full.text.size] = '\0';
+ /* read in the message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.full.text.size);
+ INIT (&bs,mail_string,(void *) LOCAL->buf,
+ elt->private.msg.full.text.size);
+ /* calculate its CRLF size */
+ elt->rfc822_size = unix_crlflen (&bs);
+ }
+ tenex_elt (stream,i); /* get current flags from file */
+ }
+}
+
+/* TENEX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *tenex_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ char *s;
+ unsigned long i;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get to header position */
+ lseek (LOCAL->fd,tenex_hdrpos (stream,msgno,&i),L_SET);
+ if (flags & FT_INTERNAL) {
+ if (i > LOCAL->buflen) { /* resize if not enough space */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get (LOCAL->buflen = i + 1);
+ }
+ /* slurp the data */
+ read (LOCAL->fd,LOCAL->buf,*length = i);
+ }
+ else {
+ s = (char *) fs_get (i + 1);/* get readin buffer */
+ s[i] = '\0'; /* tie off string */
+ read (LOCAL->fd,s,i); /* slurp the data */
+ /* make CRLF copy of string */
+ *length = unix_crlfcpy (&LOCAL->buf,&LOCAL->buflen,s,i);
+ fs_give ((void **) &s); /* free readin buffer */
+ }
+ return LOCAL->buf;
+}
+
+/* TENEX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T, always
+ */
+
+long tenex_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ char *s;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ /* get message status */
+ elt = tenex_elt (stream,msgno);
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ elt->seen = T; /* mark message as seen */
+ /* recalculate status */
+ tenex_update_status (stream,msgno,T);
+ mm_flags (stream,msgno);
+ }
+ if (flags & FT_INTERNAL) { /* if internal representation wanted */
+ /* find header position */
+ i = tenex_hdrpos (stream,msgno,&j);
+ if (i > LOCAL->buflen) { /* resize if not enough space */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get (LOCAL->buflen = i + 1);
+ }
+ /* go to text position */
+ lseek (LOCAL->fd,i + j,L_SET);
+ /* slurp the data */
+ if (read (LOCAL->fd,LOCAL->buf,i) != (long) i) return NIL;
+ /* set up stringstruct for internal */
+ INIT (bs,mail_string,LOCAL->buf,i);
+ }
+ else { /* normal form, previous text cached? */
+ if (elt->private.uid == LOCAL->uid)
+ i = elt->private.msg.text.text.size;
+ else { /* not cached, cache it now */
+ LOCAL->uid = elt->private.uid;
+ /* find header position */
+ i = tenex_hdrpos (stream,msgno,&j);
+ /* go to text position */
+ lseek (LOCAL->fd,i + j,L_SET);
+ s = (char *) fs_get ((i = tenex_size (stream,msgno) - j) + 1);
+ s[i] = '\0'; /* tie off string */
+ read (LOCAL->fd,s,i); /* slurp the data */
+ /* make CRLF copy of string */
+ i = elt->private.msg.text.text.size =
+ strcrlfcpy (&LOCAL->text.data,&LOCAL->text.size,s,i);
+ fs_give ((void **) &s); /* free readin buffer */
+ }
+ /* set up stringstruct */
+ INIT (bs,mail_string,LOCAL->text.data,i);
+ }
+ return T; /* success */
+}
+
+/* Tenex mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ */
+
+void tenex_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ struct utimbuf times;
+ struct stat sbuf;
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* make sure read comes after all that */
+ utime (stream->mailbox,&times);
+ }
+}
+
+
+/* Tenex mail per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void tenex_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ struct stat sbuf;
+ /* maybe need to do a checkpoint? */
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ LOCAL->filetime = 0; /* don't do this test for any other messages */
+ }
+ /* recalculate status */
+ tenex_update_status (stream,elt->msgno,NIL);
+}
+
+/* Tenex mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long tenex_ping (MAILSTREAM *stream)
+{
+ unsigned long i = 1;
+ long r = T;
+ int ld;
+ char lock[MAILTMPLEN];
+ struct stat sbuf;
+ if (stream && LOCAL) { /* only if stream already open */
+ fstat (LOCAL->fd,&sbuf); /* get current file poop */
+ if (LOCAL->filetime && !(LOCAL->mustcheck || LOCAL->shouldcheck) &&
+ (LOCAL->filetime < sbuf.st_mtime)) LOCAL->shouldcheck = T;
+ /* check for changed message status */
+ if (LOCAL->mustcheck || LOCAL->shouldcheck) {
+ LOCAL->filetime = sbuf.st_mtime;
+ if (LOCAL->shouldcheck) /* babble when we do this unilaterally */
+ mm_notify (stream,"[CHECK] Checking for flag updates",NIL);
+ while (i <= stream->nmsgs) tenex_elt (stream,i++);
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ }
+ /* get shared parse/append permission */
+ if ((sbuf.st_size != LOCAL->filesize) &&
+ ((ld = lockname (lock,stream->mailbox,LOCK_SH)) >= 0)) {
+ /* parse resulting mailbox */
+ r = (tenex_parse (stream)) ? T : NIL;
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ }
+ return r; /* return result of the parse */
+}
+
+
+/* Tenex mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void tenex_check (MAILSTREAM *stream)
+{
+ /* mark that a check is desired */
+ if (LOCAL) LOCAL->mustcheck = T;
+ if (tenex_ping (stream)) mm_log ("Check completed",(long) NIL);
+}
+
+/* Tenex mail expunge mailbox
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long tenex_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ struct utimbuf times;
+ struct stat sbuf;
+ off_t pos = 0;
+ int ld;
+ unsigned long i = 1;
+ unsigned long j,k,m,recent;
+ unsigned long n = 0;
+ unsigned long delta = 0;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ if (!(ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ tenex_ping (stream))); /* parse sequence if given, ping stream */
+ else if (stream->rdonly) mm_log ("Expunge ignored on readonly mailbox",WARN);
+ else {
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ }
+ /* get exclusive access */
+ if ((ld = lockname (lock,stream->mailbox,LOCK_EX)) < 0)
+ mm_log ("Unable to lock expunge mailbox",ERROR);
+ /* make sure see any newly-arrived messages */
+ else if (!tenex_parse (stream));
+ /* get exclusive access */
+ else if (flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
+ flock (LOCAL->fd,LOCK_SH);/* recover previous lock */
+ mm_log ("Can't expunge because mailbox is in use by another process",
+ ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+
+ else {
+ mm_critical (stream); /* go critical */
+ recent = stream->recent; /* get recent now that pinged and locked */
+ /* for each message */
+ while (i <= stream->nmsgs) {
+ /* get cache element */
+ elt = tenex_elt (stream,i);
+ /* number of bytes to smash or preserve */
+ k = elt->private.special.text.size + tenex_size (stream,i);
+ /* if need to expunge this message */
+ if (elt->deleted && (sequence ? elt->sequence : T)) {
+ /* if recent, note one less recent message */
+ if (elt->recent) --recent;
+ delta += k; /* number of bytes to delete */
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else if (i++ && delta) {/* preserved message */
+ /* first byte to preserve */
+ j = elt->private.special.offset;
+ do { /* read from source position */
+ m = min (k,LOCAL->buflen);
+ lseek (LOCAL->fd,j,L_SET);
+ read (LOCAL->fd,LOCAL->buf,m);
+ pos = j - delta; /* write to destination position */
+ while (T) {
+ lseek (LOCAL->fd,pos,L_SET);
+ if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+ pos += m; /* new position */
+ j += m; /* next chunk, perhaps */
+ } while (k -= m); /* until done */
+ /* note the new address of this text */
+ elt->private.special.offset -= delta;
+ }
+ /* preserved but no deleted messages */
+ else pos = elt->private.special.offset + k;
+ }
+
+ if (n) { /* truncate file after last message */
+ if (pos != (LOCAL->filesize -= delta)) {
+ sprintf (LOCAL->buf,
+ "Calculated size mismatch %lu != %lu, delta = %lu",
+ (unsigned long) pos,(unsigned long) LOCAL->filesize,delta);
+ mm_log (LOCAL->buf,WARN);
+ LOCAL->filesize = pos;/* fix it then */
+ }
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ sprintf (LOCAL->buf,"Expunged %lu messages",n);
+ /* output the news */
+ mm_log (LOCAL->buf,(long) NIL);
+ }
+ else mm_log ("No messages deleted, so no update needed",(long) NIL);
+ fsync (LOCAL->fd); /* force disk update */
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* reset atime to now */
+ utime (stream->mailbox,&times);
+ mm_nocritical (stream); /* release critical */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ flock (LOCAL->fd,LOCK_SH);/* allow sharers again */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+ }
+ return ret;
+}
+
+/* Tenex mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long tenex_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ struct utimbuf times;
+ MESSAGECACHE *elt;
+ unsigned long i,j,k;
+ long ret = LONGT;
+ int fd,ld;
+ char file[MAILTMPLEN],lock[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ /* make sure valid mailbox */
+ if (!tenex_isvalid (mailbox,file)) switch (errno) {
+ case ENOENT: /* no such file? */
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid Tenex-format mailbox name: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a Tenex-format mailbox: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* got file? */
+ if ((fd = open (file,O_BINARY|O_RDWR|O_CREAT,S_IREAD|S_IWRITE)) < 0) {
+ sprintf (LOCAL->buf,"Unable to open copy mailbox: %.80s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ mm_critical (stream); /* go critical */
+ /* get exclusive parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockname (lock,file,LOCK_EX)) < 0)) {
+ mm_log ("Unable to lock copy mailbox",ERROR);
+ mm_nocritical (stream);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
+
+ /* for each requested message */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ /* number of bytes to copy */
+ k = elt->private.special.text.size + tenex_size (stream,i);
+ do { /* read from source position */
+ j = min (k,LOCAL->buflen);
+ read (LOCAL->fd,LOCAL->buf,j);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ } while (ret && (k -= j));/* until done */
+ }
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE)) {
+ sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ }
+ /* set atime to now-1 if successful copy */
+ if (ret) times.actime = time (0) - 1;
+ /* else preserved \Marked status */
+ else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
+ sbuf.st_atime : time (0);
+ times.modtime = sbuf.st_mtime;/* preserve mtime */
+ utime (file,&times); /* set the times */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ close (fd); /* close the file */
+ mm_nocritical (stream); /* release critical */
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE)) {
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = tenex_elt (stream,i))->sequence) {
+ elt->deleted = T; /* mark message deleted */
+ /* recalculate status */
+ tenex_update_status (stream,i,NIL);
+ }
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* make sure atime remains greater */
+ utime (stream->mailbox,&times);
+ }
+ }
+ if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
+ mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN);
+ return ret;
+}
+
+/* Tenex mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long tenex_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd,ld,c;
+ char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ struct utimbuf times;
+ FILE *df;
+ MESSAGECACHE elt;
+ long f;
+ unsigned long i,j,uf,size;
+ STRING *message;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = &tenexproto;
+ /* make sure valid mailbox */
+ if (!tenex_isvalid (mailbox,file)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!compare_cstring (mailbox,"INBOX")) tenex_create (NIL,"INBOX");
+ else {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* falls through */
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid TENEX-format mailbox name: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a TENEX-format mailbox: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get first message */
+ if (!(*af) (stream,data,&flags,&date,&message)) return NIL;
+
+ /* open destination mailbox */
+ if (((fd = open (file,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IREAD|S_IWRITE))
+ < 0) || !(df = fdopen (fd,"ab"))) {
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockname (lock,file,LOCK_EX)) < 0)) {
+ mm_log ("Unable to lock append mailbox",ERROR);
+ close (fd);
+ return NIL;
+ }
+ mm_critical (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ errno = 0;
+ do { /* parse flags */
+ if (!SIZE (message)) { /* guard against zero-length */
+ mm_log ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ f = mail_parse_flags (stream,flags,&i);
+ /* reverse bits (dontcha wish we had CIRC?) */
+ for (uf = 0; i; uf |= 1 << (29 - find_rightmost_bit (&i)));
+ if (date) { /* parse date if given */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ mm_log (tmp,ERROR);
+ ret = NIL; /* mark failure */
+ break;
+ }
+ mail_date (tmp,&elt); /* write preseved date */
+ }
+ else internal_date (tmp); /* get current date in IMAP format */
+ i = GETPOS (message); /* remember current position */
+ for (j = SIZE (message), size = 0; j; --j)
+ if (SNX (message) != '\015') ++size;
+ SETPOS (message,i); /* restore position */
+ /* write header */
+ if (fprintf (df,"%s,%lu;%010lo%02lo\n",tmp,size,uf,(unsigned long) f) < 0)
+ ret = NIL;
+ else { /* write message */
+ while (size) if ((c = 0xff & SNX (message)) != '\015') {
+ if (putc (c,df) != EOF) --size;
+ else break;
+ }
+ /* get next message */
+ if (size || !(*af) (stream,data,&flags,&date,&message)) ret = NIL;
+ }
+ } while (ret && message);
+ /* if error... */
+ if (!ret || (fflush (df) == EOF)) {
+ ftruncate (fd,sbuf.st_size);/* revert file */
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ if (errno) {
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+ ret = NIL;
+ }
+ if (ret) times.actime = time (0) - 1;
+ /* else preserved \Marked status */
+ else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
+ sbuf.st_atime : time (0);
+ times.modtime = sbuf.st_mtime;/* preserve mtime */
+ utime (file,&times); /* set the times */
+ fclose (df); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ mm_nocritical (stream); /* release critical */
+ if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
+ mm_log ("Can not return meaningful APPENDUID with this mailbox format",
+ WARN);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* Tenex mail return internal message size in bytes
+ * Accepts: MAIL stream
+ * message #
+ * Returns: internal size of message
+ */
+
+unsigned long tenex_size (MAILSTREAM *stream,unsigned long m)
+{
+ MESSAGECACHE *elt = mail_elt (stream,m);
+ return ((m < stream->nmsgs) ? mail_elt (stream,m+1)->private.special.offset :
+ LOCAL->filesize) -
+ (elt->private.special.offset + elt->private.special.text.size);
+}
+
+/* Tenex mail parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long tenex_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = NIL;
+ unsigned char c,*s,*t,*x;
+ char tmp[MAILTMPLEN];
+ unsigned long i,j;
+ long curpos = LOCAL->filesize;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ short added = NIL;
+ short silent = stream->silent;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %ld to %ld!",curpos,sbuf.st_size);
+ mm_log (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ stream->silent = T; /* don't pass up mm_exists() events yet */
+ while (sbuf.st_size - curpos){/* while there is stuff to parse */
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,L_SET);
+ if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
+ sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size,
+ i ? strerror (errno) : "no data read");
+ mm_log (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
+ if (!(s = strchr (LOCAL->buf,'\012'))) {
+ sprintf (tmp,"Unable to find newline at %lu in %lu bytes, text: %s",
+ (unsigned long) curpos,i,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ *s = '\0'; /* tie off header line */
+ i = (s + 1) - LOCAL->buf; /* note start of text offset */
+ if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
+ sprintf (tmp,"Unable to parse internal header at %lu: %s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ mm_log (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ *s++ = '\0'; *t++ = '\0'; /* tie off fields */
+
+ added = T; /* note that a new message was added */
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ /* instantiate an elt for this message */
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ elt->private.uid = ++stream->uid_last;
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* in case error */
+ elt->private.special.text.size = 0;
+ /* header size not known yet */
+ elt->private.msg.header.text.size = 0;
+ x = s; /* parse the header components */
+ if (mail_parse_date (elt,LOCAL->buf) &&
+ (elt->private.msg.full.text.size = strtoul (s,(char **) &s,10)) &&
+ (!(s && *s)) && isdigit (t[0]) && isdigit (t[1]) && isdigit (t[2]) &&
+ isdigit (t[3]) && isdigit (t[4]) && isdigit (t[5]) &&
+ isdigit (t[6]) && isdigit (t[7]) && isdigit (t[8]) &&
+ isdigit (t[9]) && isdigit (t[10]) && isdigit (t[11]) && !t[12])
+ elt->private.special.text.size = i;
+ else { /* oops */
+ sprintf (tmp,"Unable to parse internal header elements at %ld: %s,%s;%s",
+ curpos,(char *) LOCAL->buf,(char *) x,(char *) t);
+ mm_log (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ /* make sure didn't run off end of file */
+ if ((curpos += (elt->private.msg.full.text.size + i)) > sbuf.st_size) {
+ sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
+ elt->private.special.offset,(unsigned long) curpos,
+ (unsigned long) sbuf.st_size);
+ mm_log (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ c = t[10]; /* remember first system flags byte */
+ t[10] = '\0'; /* tie off flags */
+ j = strtoul (t,NIL,8); /* get user flags value */
+ t[10] = c; /* restore first system flags byte */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ /* calculate system flags */
+ if ((j = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T;
+ if (j & fDELETED) elt->deleted = T;
+ if (j & fFLAGGED) elt->flagged = T;
+ if (j & fANSWERED) elt->answered = T;
+ if (j & fDRAFT) elt->draft = T;
+ if (!(j & fOLD)) { /* newly arrived message? */
+ elt->recent = T;
+ recent++; /* count up a new recent message */
+ /* mark it as old */
+ tenex_update_status (stream,nmsgs,NIL);
+ }
+ }
+ fsync (LOCAL->fd); /* make sure all the fOLD flags take */
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
+ LOCAL->filetime = sbuf.st_mtime;
+ if (added && !stream->rdonly){/* make sure atime updated */
+ struct utimbuf times;
+ times.actime = time (0);
+ times.modtime = LOCAL->filetime;
+ utime (stream->mailbox,&times);
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return LONGT; /* return the winnage */
+}
+
+/* Tenex get cache element with status updating from file
+ * Accepts: MAIL stream
+ * message number
+ * Returns: cache element
+ */
+
+MESSAGECACHE *tenex_elt (MAILSTREAM *stream,unsigned long msgno)
+{
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
+ old.answered = elt->answered; old.draft = elt->draft;
+ old.user_flags = elt->user_flags;
+ tenex_read_flags (stream,elt);
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
+ (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
+ mm_flags (stream,msgno); /* let top level know */
+ return elt;
+}
+
+
+/* Tenex read flags from file
+ * Accepts: MAIL stream
+ * Returns: cache element
+ */
+
+void tenex_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ unsigned long i,j;
+ /* noop if readonly and have valid flags */
+ if (stream->rdonly && elt->valid) return;
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 13,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,12) < 0) {
+ sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ /* calculate system flags */
+ i = (((LOCAL->buf[10]-'0') * 8) + LOCAL->buf[11]-'0');
+ elt->seen = i & fSEEN ? T : NIL; elt->deleted = i & fDELETED ? T : NIL;
+ elt->flagged = i & fFLAGGED ? T : NIL;
+ elt->answered = i & fANSWERED ? T : NIL; elt->draft = i & fDRAFT ? T : NIL;
+ LOCAL->buf[10] = '\0'; /* tie off flags */
+ j = strtoul(LOCAL->buf,NIL,8);/* get user flags value */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ elt->valid = T; /* have valid flags now */
+}
+
+/* Tenex update status string
+ * Accepts: MAIL stream
+ * message number
+ * flag saying whether or not to sync
+ */
+
+void tenex_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag)
+{
+ struct utimbuf times;
+ struct stat sbuf;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ unsigned long j,k = 0;
+ /* readonly */
+ if (stream->rdonly || !elt->valid) tenex_read_flags (stream,elt);
+ else { /* readwrite */
+ j = elt->user_flags; /* get user flags */
+ /* reverse bits (dontcha wish we had CIRC?) */
+ while (j) k |= 1 << (29 - find_rightmost_bit (&j));
+ /* print new flag string */
+ sprintf (LOCAL->buf,"%010lo%02o",k,(unsigned)
+ (fOLD + (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ while (T) { /* get to that place in the file */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 13,L_SET);
+ /* write new flags */
+ if (write (LOCAL->fd,LOCAL->buf,12) > 0) break;
+ mm_notify (stream,strerror (errno),WARN);
+ mm_diskerror (stream,errno,T);
+ }
+ if (syncflag) { /* sync if requested */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ times.modtime = LOCAL->filetime = sbuf.st_mtime;
+ times.actime = time (0); /* make sure read is later */
+ utime (stream->mailbox,&times);
+ }
+ }
+}
+
+/* Tenex locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * Returns: position of header in file
+ */
+
+unsigned long tenex_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size)
+{
+ unsigned long siz;
+ long i = 0;
+ char c = '\0';
+ char *s = NIL;
+ MESSAGECACHE *elt = tenex_elt (stream,msgno);
+ unsigned long ret = elt->private.special.offset +
+ elt->private.special.text.size;
+ unsigned long msiz = tenex_size (stream,msgno);
+ /* is header size known? */
+ if (!(*size = elt->private.msg.header.text.size)) {
+ lseek (LOCAL->fd,ret,L_SET);/* get to header position */
+ /* search message for LF LF */
+ for (siz = 0; siz < msiz; siz++) {
+ if (--i <= 0) /* read another buffer as necessary */
+ read (LOCAL->fd,s = LOCAL->buf,i = min (msiz-siz,(long) MAILTMPLEN));
+ /* two newline sequence? */
+ if ((c == '\012') && (*s == '\012')) {
+ /* yes, note for later */
+ elt->private.msg.header.text.size = (*size = siz + 1);
+ return ret; /* return to caller */
+ }
+ else c = *s++; /* next character */
+ }
+ /* header consumes entire message */
+ elt->private.msg.header.text.size = *size = msiz;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/os2/unixnt.c b/imap/src/osdep/os2/unixnt.c
new file mode 100644
index 00000000..2e92d39a
--- /dev/null
+++ b/imap/src/osdep/os2/unixnt.c
@@ -0,0 +1,2297 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX mail routines
+ *
+ * Author: Mark Crispin
+ * UW Technology
+ * University of Washington
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 20 December 1989
+ * Last Edited: 27 March 2008
+ */
+
+
+/* DEDICATION
+ *
+ * This file is dedicated to my dog, Unix, also known as Yun-chan and
+ * Unix J. Terwilliker Jehosophat Aloysius Monstrosity Animal Beast. Unix
+ * passed away at the age of 11 1/2 on September 14, 1996, 12:18 PM PDT, after
+ * a two-month bout with cirrhosis of the liver.
+ *
+ * He was a dear friend, and I miss him terribly.
+ *
+ * Lift a leg, Yunie. Luv ya forever!!!!
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/utime.h>
+#include "unixnt.h"
+#include "pseudo.h"
+#include "fdstring.h"
+#include "misc.h"
+#include "dummy.h"
+
+/* UNIX I/O stream local data */
+
+typedef struct unix_local {
+ unsigned int dirty : 1; /* disk copy needs updating */
+ unsigned int ddirty : 1; /* double-dirty, ping becomes checkpoint */
+ unsigned int pseudo : 1; /* uses a pseudo message */
+ unsigned int appending : 1; /* don't mark new messages as old */
+ int fd; /* mailbox file descriptor */
+ int ld; /* lock file descriptor */
+ char *lname; /* lock file name */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned long uid; /* current text uid */
+ SIZEDTEXT text; /* current text */
+ unsigned long textlen; /* current text length */
+ char *line; /* returned line */
+ char *linebuf; /* line readin buffer */
+ unsigned long linebuflen; /* current line readin buffer length */
+} UNIXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((UNIXLOCAL *) stream->local)
+
+
+/* UNIX protected file structure */
+
+typedef struct unix_file {
+ MAILSTREAM *stream; /* current stream */
+ off_t curpos; /* current file position */
+ off_t protect; /* protected position */
+ off_t filepos; /* current last written file position */
+ char *buf; /* overflow buffer */
+ size_t buflen; /* current overflow buffer length */
+ char *bufpos; /* current buffer position */
+} UNIXFILE;
+
+/* Function prototypes */
+
+DRIVER *unix_valid (char *name);
+void *unix_parameters (long function,void *value);
+void unix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void unix_list (MAILSTREAM *stream,char *ref,char *pat);
+void unix_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long unix_create (MAILSTREAM *stream,char *mailbox);
+long unix_delete (MAILSTREAM *stream,char *mailbox);
+long unix_rename (MAILSTREAM *stream,char *old,char *newname);
+MAILSTREAM *unix_open (MAILSTREAM *stream);
+void unix_close (MAILSTREAM *stream,long options);
+char *unix_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long unix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+char *unix_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
+ unsigned long *length,long flags);
+void unix_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long unix_ping (MAILSTREAM *stream);
+void unix_check (MAILSTREAM *stream);
+long unix_expunge (MAILSTREAM *stream,char *sequence,long options);
+long unix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long unix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+int unix_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg);
+int unix_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set);
+
+void unix_abort (MAILSTREAM *stream);
+char *unix_file (char *dst,char *name);
+int unix_lock (char *file,int flags,int mode,char *lock,int op);
+void unix_unlock (int fd,MAILSTREAM *stream,char *lock);
+int unix_parse (MAILSTREAM *stream,char *lock,int op);
+char *unix_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size);
+unsigned long unix_pseudo (MAILSTREAM *stream,char *hdr);
+unsigned long unix_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
+ unsigned long uid,long flag);
+long unix_rewrite (MAILSTREAM *stream,unsigned long *nexp,char *lock,
+ long flags);
+long unix_extend (MAILSTREAM *stream,unsigned long size);
+void unix_write (UNIXFILE *f,char *s,unsigned long i);
+void unix_phys_write (UNIXFILE *f,char *buf,size_t size);
+
+/* UNIX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER unixdriver = {
+ "unix", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_NONEWMAILRONLY|DR_XPOINT,
+ (DRIVER *) NIL, /* next driver */
+ unix_valid, /* mailbox is valid for us */
+ unix_parameters, /* manipulate parameters */
+ unix_scan, /* scan mailboxes */
+ unix_list, /* list mailboxes */
+ unix_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ unix_create, /* create mailbox */
+ unix_delete, /* delete mailbox */
+ unix_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ unix_open, /* open mailbox */
+ unix_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ unix_header, /* fetch message header */
+ unix_text, /* fetch message text */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ unix_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ unix_ping, /* ping mailbox to see if still alive */
+ unix_check, /* check for new messages */
+ unix_expunge, /* expunge deleted messages */
+ unix_copy, /* copy messages to another mailbox */
+ unix_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM unixproto = {&unixdriver};
+
+ /* driver parameters */
+static long unix_fromwidget = T;
+
+/* UNIX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *unix_valid (char *name)
+{
+ int fd;
+ DRIVER *ret = NIL;
+ int c,r;
+ char tmp[MAILTMPLEN],file[MAILTMPLEN],*s,*t;
+ struct stat sbuf;
+ struct utimbuf times;
+ errno = EINVAL; /* assume invalid argument */
+ /* must be non-empty file */
+ if ((t = dummy_file (file,name)) && !stat (t,&sbuf) &&
+ ((sbuf.st_mode & S_IFMT) == S_IFREG)) {
+ if (!sbuf.st_size)errno = 0;/* empty file */
+ else if ((fd = open (file,O_BINARY|O_RDONLY,NIL)) >= 0) {
+ memset (tmp,'\0',MAILTMPLEN);
+ if (read (fd,tmp,MAILTMPLEN-1) <= 0) errno = -1;
+ else { /* ignore leading whitespace */
+ for (s = tmp,c = '\n';
+ (*s == '\r') || (*s == '\n') || (*s == ' ') || (*s == '\t');
+ c = *s++);
+ if (c == '\n') { /* at start of a line? */
+ VALID (s,t,r,c); /* yes, validate format */
+ if (r) ret = &unixdriver;
+ else errno = -1; /* invalid format */
+ }
+ }
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) {
+ /* yes, preserve atime and mtime */
+ times.actime = sbuf.st_atime;
+ times.modtime = sbuf.st_mtime;
+ utime (file,&times); /* set the times */
+ }
+ }
+ }
+ return ret; /* return what we should */
+}
+/* UNIX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *unix_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_FROMWIDGET:
+ unix_fromwidget = (long) value;
+ case GET_FROMWIDGET:
+ ret = (void *) unix_fromwidget;
+ break;
+ }
+ return ret;
+}
+
+/* UNIX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void unix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* UNIX mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void unix_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* UNIX mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void unix_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* UNIX mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long unix_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN];
+ long ret = NIL;
+ int fd;
+ time_t ti = time (0);
+ if (!(s = dummy_file (mbx,mailbox))) {
+ sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
+ mm_log (tmp,ERROR);
+ }
+ /* create underlying file */
+ else if (dummy_create_path (stream,s,NIL)) {
+ if ((s = strrchr (s,'\\')) && !s[1]) ret = T;
+ if ((fd = open (mbx,O_WRONLY|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
+ mm_log (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else { /* initialize header */
+ memset (tmp,'\0',MAILTMPLEN);
+ sprintf (tmp,"From %s %s",pseudo_from,ctime (&ti));
+ if (s = strpbrk (tmp,"\r\n")) *s = '\0';
+ strcat (tmp,"\r\nDate: ");
+ rfc822_fixed_date (s = tmp + strlen (tmp));
+ sprintf (s += strlen (s), /* write the pseudo-header */
+ "\r\nFrom: %s <%s@%s>\r\nSubject: %s\r\nX-IMAP: %010lu 0000000000\r\nStatus: RO\r\n\r\n%s\r\n\r\n",
+ pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
+ (unsigned long) ti,pseudo_msg);
+ if (write (fd,tmp,strlen (tmp)) > 0) {
+ close (fd); /* close file */
+ ret = T;
+ }
+ else {
+ sprintf (tmp,"Can't initialize mailbox node %.80s: %s",mbx,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ close (fd); /* close file before unlinking */
+ unlink (mbx); /* delete the file */
+ }
+ }
+ }
+ return ret;
+}
+
+/* UNIX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long unix_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return unix_rename (stream,mailbox,NIL);
+}
+
+
+/* UNIX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long unix_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = NIL;
+ char c,*s = NIL;
+ char tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN],lockx[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ mm_critical (stream); /* get the c-client lock */
+ if (!dummy_file (file,old) ||
+ (newname && (!(s = dummy_file (tmp,newname)) ||
+ ((s = strrchr (s,'\\')) && !s[1]))))
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ else if ((ld = lockname (lock,file,NIL)) < 0)
+ sprintf (tmp,"Can't get lock for mailbox %.80s",old);
+
+ else { /* lock out other c-clients */
+ if (flock (ld,LOCK_EX|LOCK_NB)) {
+ close (ld); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ }
+ /* lock out non c-client applications */
+ else if ((fd = unix_lock (file,O_BINARY|O_RDWR,S_IREAD|S_IWRITE,lockx,
+ LOCK_EX)) < 0)
+ sprintf (tmp,"Can't lock mailbox %.80s: %s",old,strerror (errno));
+ else {
+ unix_unlock(fd,NIL,lockx);/* pacify evil NTFS */
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if ((s = strrchr (tmp,'\\')) && (s != tmp) &&
+ ((tmp[1] != ':') || (s != tmp + 2))) {
+ c = s[1]; /* remember character after delimiter */
+ *s = s[1] = '\0'; /* tie off name at delimiter */
+ /* name doesn't exist, create it */
+ if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
+ *s = '\\'; /* restore delimiter */
+ if (!dummy_create (stream,newname)) {
+ flock (ld,LOCK_UN);
+ close (ld); /* close c-client lock */
+ unlink (lock); /* and delete it */
+ mm_nocritical (stream);
+ return NIL; /* couldn't create superior */
+ }
+ }
+ else *s = '\\'; /* restore delimiter */
+ s[1] = c; /* restore character after delimiter */
+ }
+ if (rename (file,tmp))
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ else ret = T; /* set success */
+ }
+ else if (unlink (file)) /* want delete */
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ else ret = T; /* set success */
+ flock (ld,LOCK_UN); /* release c-client lock */
+ close (ld); /* close c-client lock */
+ unlink (lock); /* and delete it */
+ }
+ }
+ mm_nocritical (stream); /* no longer critical */
+ if (!ret) mm_log (tmp,ERROR); /* log error */
+ return ret; /* return success or failure */
+}
+
+/* UNIX mail open
+ * Accepts: Stream to open
+ * Returns: Stream on success, NIL on failure
+ */
+
+MAILSTREAM *unix_open (MAILSTREAM *stream)
+{
+ int fd;
+ char tmp[MAILTMPLEN];
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &unixproto;
+ if (stream->local) fatal ("unix recycle stream");
+ stream->local = memset (fs_get (sizeof (UNIXLOCAL)),0,sizeof (UNIXLOCAL));
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ /* canonicalize the stream mailbox name */
+ if (!dummy_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* flush old name */
+ fs_give ((void **) &stream->mailbox);
+ /* save canonical name */
+ stream->mailbox = cpystr (tmp);
+ LOCAL->fd = LOCAL->ld = -1; /* no file or state locking yet */
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = CHUNKSIZE) + 1);
+ LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE);
+ LOCAL->text.size = CHUNKSIZE - 1;
+ LOCAL->linebuf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->linebuflen = CHUNKSIZE - 1;
+ stream->sequence++; /* bump sequence number */
+ if (!stream->rdonly) { /* make lock for read/write access */
+ if ((fd = lockname (tmp,stream->mailbox,NIL)) < 0)
+ mm_log ("Can't open mailbox lock, access is readonly",WARN);
+ /* can get the lock? */
+ else if (flock (fd,LOCK_EX|LOCK_NB)) {
+ if (!stream->silent)
+ mm_log ("Mailbox is open by another process, access is readonly",WARN);
+ close (fd);
+ }
+ else { /* got the lock, nobody else can alter state */
+ LOCAL->ld = fd; /* note lock's fd and name */
+ LOCAL->lname = cpystr (tmp);
+ }
+ }
+
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ /* will we be able to get write access? */
+ if ((LOCAL->ld >= 0) && access (stream->mailbox,02) && (errno == EACCES)) {
+ mm_log ("Can't get write access to mailbox, access is readonly",WARN);
+ flock (LOCAL->ld,LOCK_UN); /* release the lock */
+ close (LOCAL->ld); /* close the lock file */
+ LOCAL->ld = -1; /* no more lock fd */
+ unlink (LOCAL->lname); /* delete it */
+ }
+ /* reset UID validity */
+ stream->uid_validity = stream->uid_last = 0;
+ if (stream->silent && !stream->rdonly && (LOCAL->ld < 0))
+ unix_abort (stream); /* abort if can't get RW silent stream */
+ /* parse mailbox */
+ else if (unix_parse (stream,tmp,LOCK_SH)) {
+ unix_unlock (LOCAL->fd,stream,tmp);
+ mail_unlock (stream);
+ mm_nocritical (stream); /* done with critical */
+ }
+ if (!LOCAL) return NIL; /* failure if stream died */
+ /* make sure upper level knows readonly */
+ stream->rdonly = (LOCAL->ld < 0);
+ /* notify about empty mailbox */
+ if (!(stream->nmsgs || stream->silent)) mm_log ("Mailbox is empty",NIL);
+ if (!stream->rdonly) { /* flags stick if readwrite */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft = T;
+ /* have permanent keywords */
+ stream->perm_user_flags = 0xffffffff;
+ /* and maybe can create them too */
+ stream->kwd_create = stream->user_flags[NUSERFLAGS-1] ? NIL : T;
+ }
+ return stream; /* return stream alive to caller */
+}
+
+
+/* UNIX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void unix_close (MAILSTREAM *stream,long options)
+{
+ int silent = stream->silent;
+ stream->silent = T; /* go silent */
+ /* expunge if requested */
+ if (options & CL_EXPUNGE) unix_expunge (stream,NIL,NIL);
+ /* else dump final checkpoint */
+ else if (LOCAL->dirty) unix_check (stream);
+ stream->silent = silent; /* restore old silence state */
+ unix_abort (stream); /* now punt the file and local data */
+}
+
+/* UNIX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+ /* lines to filter from header */
+static STRINGLIST *unix_hlines = NIL;
+
+char *unix_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned char *s;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get cache */
+ if (!unix_hlines) { /* once only code */
+ STRINGLIST *lines = unix_hlines = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "Status"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-Status"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-Keywords"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-UID"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-IMAP"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-IMAPbase"));
+ }
+ /* go to header position */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.msg.header.offset,L_SET);
+
+ if (flags & FT_INTERNAL) { /* initial data OK? */
+ if (elt->private.msg.header.text.size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
+ elt->private.msg.header.text.size) + 1);
+ }
+ /* read message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.header.text.size);
+ /* got text, tie off string */
+ LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0';
+ }
+ else { /* need to make a CRLF version */
+ read (LOCAL->fd,s = (char *) fs_get (elt->private.msg.header.text.size+1),
+ elt->private.msg.header.text.size);
+ /* tie off string, and convert to CRLF */
+ s[elt->private.msg.header.text.size] = '\0';
+ *length = unix_crlfcpy (&LOCAL->buf,&LOCAL->buflen,s,
+ elt->private.msg.header.text.size);
+ fs_give ((void **) &s); /* free readin buffer */
+ }
+ *length = mail_filter (LOCAL->buf,*length,unix_hlines,FT_NOT);
+ return LOCAL->buf; /* return processed copy */
+}
+
+/* UNIX mail fetch message text
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL if failure
+ */
+
+long unix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ char *s;
+ unsigned long i;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);/* get cache element */
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ /* mark message seen and dirty */
+ elt->seen = elt->private.dirty = LOCAL->dirty = T;
+ mm_flags (stream,msgno);
+ }
+ s = unix_text_work (stream,elt,&i,flags);
+ INIT (bs,mail_string,s,i); /* set up stringstruct */
+ return T; /* success */
+}
+
+/* UNIX mail fetch message text worker routine
+ * Accepts: MAIL stream
+ * message cache element
+ * pointer to returned header text length
+ * option flags
+ */
+
+char *unix_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
+ unsigned long *length,long flags)
+{
+ FDDATA d;
+ STRING bs;
+ unsigned char c,*s,tmp[CHUNKSIZE];
+ /* go to text position */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.msg.text.offset,L_SET);
+ if (flags & FT_INTERNAL) { /* initial data OK? */
+ if (elt->private.msg.text.text.size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
+ elt->private.msg.text.text.size) + 1);
+ }
+ /* read message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size);
+ /* got text, tie off string */
+ LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0';
+ return LOCAL->buf;
+ }
+ /* have it cached already? */
+ if (elt->private.uid != LOCAL->uid) {
+ /* not cached, cache it now */
+ LOCAL->uid = elt->private.uid;
+ /* is buffer big enough? */
+ if (elt->rfc822_size > LOCAL->text.size) {
+ /* excessively conservative, but the right thing is too hard to do */
+ fs_give ((void **) &LOCAL->text.data);
+ LOCAL->text.data = (unsigned char *)
+ fs_get ((LOCAL->text.size = elt->rfc822_size) + 1);
+ }
+ d.fd = LOCAL->fd; /* yes, set up file descriptor */
+ d.pos = elt->private.special.offset + elt->private.msg.text.offset;
+ d.chunk = tmp; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* file chunk size */
+ INIT (&bs,fd_string,&d,elt->private.msg.text.text.size);
+ for (s = (char *) LOCAL->text.data; SIZE (&bs);) switch (c = SNX (&bs)) {
+ case '\r': /* carriage return seen */
+ *s++ = c; /* copy it and any succeeding LF */
+ if (SIZE (&bs) && (CHR (&bs) == '\n')) *s++ = SNX (&bs);
+ break;
+ case '\n':
+ *s++ = '\r'; /* insert a CR */
+ default:
+ *s++ = c; /* copy characters */
+ }
+ *s = '\0'; /* tie off buffer */
+ /* calculate length of cached data */
+ LOCAL->textlen = s - LOCAL->text.data;
+ }
+ *length = LOCAL->textlen; /* return from cache */
+ return (char *) LOCAL->text.data;
+}
+
+/* UNIX per-message modify flag
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void unix_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ /* only after finishing */
+ if (elt->valid) elt->private.dirty = LOCAL->dirty = T;
+}
+
+
+/* UNIX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long unix_ping (MAILSTREAM *stream)
+{
+ char lock[MAILTMPLEN];
+ struct stat sbuf;
+ /* big no-op if not readwrite */
+ if (LOCAL && (LOCAL->ld >= 0) && !stream->lock) {
+ if (stream->rdonly) { /* does he want to give up readwrite? */
+ /* checkpoint if we changed something */
+ if (LOCAL->dirty) unix_check (stream);
+ flock (LOCAL->ld,LOCK_UN);/* release readwrite lock */
+ close (LOCAL->ld); /* close the readwrite lock file */
+ LOCAL->ld = -1; /* no more readwrite lock fd */
+ unlink (LOCAL->lname); /* delete the readwrite lock file */
+ }
+ else { /* get current mailbox size */
+ if (LOCAL->fd >= 0) fstat (LOCAL->fd,&sbuf);
+ else if (stat (stream->mailbox,&sbuf)) {
+ sprintf (LOCAL->buf,"Mailbox stat failed, aborted: %s",
+ strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ unix_abort (stream);
+ return NIL;
+ }
+ /* parse if mailbox changed */
+ if ((LOCAL->ddirty || (sbuf.st_size != LOCAL->filesize)) &&
+ unix_parse (stream,lock,LOCK_EX)) {
+ /* force checkpoint if double-dirty */
+ if (LOCAL->ddirty) unix_rewrite (stream,NIL,lock,NIL);
+ /* unlock mailbox */
+ else unix_unlock (LOCAL->fd,stream,lock);
+ mail_unlock (stream); /* and stream */
+ mm_nocritical (stream); /* done with critical */
+ }
+ }
+ }
+ return LOCAL ? LONGT : NIL; /* return if still alive */
+}
+
+/* UNIX mail check mailbox
+ * Accepts: MAIL stream
+ */
+
+void unix_check (MAILSTREAM *stream)
+{
+ char lock[MAILTMPLEN];
+ /* parse and lock mailbox */
+ if (LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
+ unix_parse (stream,lock,LOCK_EX)) {
+ /* any unsaved changes? */
+ if (LOCAL->dirty && unix_rewrite (stream,NIL,lock,NIL)) {
+ if (!stream->silent) mm_log ("Checkpoint completed",NIL);
+ }
+ /* no checkpoint needed, just unlock */
+ else unix_unlock (LOCAL->fd,stream,lock);
+ mail_unlock (stream); /* unlock the stream */
+ mm_nocritical (stream); /* done with critical */
+ }
+}
+
+
+/* UNIX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long unix_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ unsigned long i;
+ char lock[MAILTMPLEN];
+ char *msg = NIL;
+ /* parse and lock mailbox */
+ if (ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
+ unix_parse (stream,lock,LOCK_EX)) {
+ /* check expunged messages if not dirty */
+ for (i = 1; !LOCAL->dirty && (i <= stream->nmsgs); i++) {
+ MESSAGECACHE *elt = mail_elt (stream,i);
+ if (mail_elt (stream,i)->deleted) LOCAL->dirty = T;
+ }
+ if (!LOCAL->dirty) { /* not dirty and no expunged messages */
+ unix_unlock (LOCAL->fd,stream,lock);
+ msg = "No messages deleted, so no update needed";
+ }
+ else if (unix_rewrite (stream,&i,lock,sequence ? LONGT : NIL)) {
+ if (i) sprintf (msg = LOCAL->buf,"Expunged %lu messages",i);
+ else msg = "Mailbox checkpointed, but no messages expunged";
+ }
+ /* rewrite failed */
+ else unix_unlock (LOCAL->fd,stream,lock);
+ mail_unlock (stream); /* unlock the stream */
+ mm_nocritical (stream); /* done with critical */
+ if (msg && !stream->silent) mm_log (msg,NIL);
+ }
+ else if (!stream->silent) mm_log("Expunge ignored on readonly mailbox",WARN);
+ return ret;
+}
+
+/* UNIX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long unix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ int fd;
+ char *s,file[MAILTMPLEN],lock[MAILTMPLEN];
+ struct utimbuf times;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ long ret = T;
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ copyuid_t cu = (copyuid_t) (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ?
+ NIL : mail_parameters (NIL,GET_COPYUID,NIL));
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ MAILSTREAM *tstream = NIL;
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* make sure destination is valid */
+ if (!(unix_valid (mailbox) || !errno))
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ }
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ unix_create (NIL,"INBOX");/* create empty INBOX */
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid UNIX-format mailbox name: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a UNIX-format mailbox: %.80s",mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+
+ /* try to open rewrite for UIDPLUS */
+ if ((tstream = mail_open_work (&unixdriver,NIL,mailbox,
+ OP_SILENT|OP_NOKOD)) && tstream->rdonly)
+ tstream = mail_close (tstream);
+ if (cu && !tstream) { /* wanted a COPYUID? */
+ sprintf (LOCAL->buf,"Unable to write-open mailbox for COPYUID: %.80s",
+ mailbox);
+ MM_LOG (LOCAL->buf,WARN);
+ cu = NIL; /* don't try to do COPYUID */
+ }
+ LOCAL->buf[0] = '\0';
+ mm_critical (stream); /* go critical */
+ if ((fd = unix_lock (dummy_file (file,mailbox),
+ O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IREAD|S_IWRITE,
+ lock,LOCK_EX)) < 0) {
+ mm_nocritical (stream); /* done with critical */
+ sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
+ mm_log (LOCAL->buf,ERROR); /* log the error */
+ return NIL; /* failed */
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ /* write all requested messages to mailbox */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
+ if (LOCAL->buf[(j = elt->private.special.text.size) - 2] != '\r') {
+ LOCAL->buf[j - 1] = '\r';
+ LOCAL->buf[j++] = '\n';
+ }
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ else { /* internal header succeeded */
+ s = unix_header (stream,i,&j,NIL);
+ /* header size, sans trailing newline */
+ if (j && (s[j - 4] == '\r')) j -= 2;
+ if (write (fd,s,j) < 0) ret = NIL;
+ else { /* message header succeeded */
+ j = tstream ? /* write UIDPLUS data if have readwrite */
+ unix_xstatus (stream,LOCAL->buf,elt,++(tstream->uid_last),LONGT) :
+ unix_xstatus (stream,LOCAL->buf,elt,NIL,NIL);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ else { /* message status succeeded */
+ s = unix_text_work (stream,elt,&j,NIL);
+ if ((write (fd,s,j) < 0) || (write (fd,"\r\n",2) < 0))
+ ret = NIL;
+ else if (cu) { /* need to pass back new UID? */
+ mail_append_set (source,mail_uid (stream,i));
+ mail_append_set (dest,tstream->uid_last);
+ }
+ }
+ }
+ }
+ }
+
+ if (!ret || fsync (fd)) { /* force out the update */
+ sprintf (LOCAL->buf,"Message copy failed: %s",strerror (errno));
+ ftruncate (fd,sbuf.st_size);
+ ret = NIL;
+ }
+ /* force UIDVALIDITY assignment now */
+ if (tstream && !tstream->uid_validity)
+ tstream->uid_validity = (unsigned long) time (0);
+ /* return sets if doing COPYUID */
+ if (cu && ret) (*cu) (stream,mailbox,tstream->uid_validity,source,dest);
+ else { /* flush any sets we may have built */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ times.modtime = time (0); /* set mtime to now */
+ /* set atime to now-1 if successful copy */
+ if (ret) times.actime = times.modtime - 1;
+
+ else times.actime = /* else preserve \Marked status */
+ ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
+ sbuf.st_atime : times.modtime;
+ utime (file,&times); /* set the times */
+ unix_unlock (fd,NIL,lock); /* unlock and close mailbox */
+ if (tstream) { /* update last UID if we can */
+ UNIXLOCAL * local = (UNIXLOCAL *) tstream->local;
+ local->dirty = T; /* do a rewrite */
+ local->appending = T; /* but not at the cost of marking as old */
+ tstream = mail_close (tstream);
+ }
+ /* log the error */
+ if (!ret) mm_log (LOCAL->buf,ERROR);
+ /* delete if requested message */
+ else if (options & CP_MOVE) for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence)
+ elt->deleted = elt->private.dirty = LOCAL->dirty = T;
+ mm_nocritical (stream); /* release critical */
+ return ret;
+}
+
+/* UNIX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+#define BUFLEN 8*MAILTMPLEN
+
+long unix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd;
+ unsigned long i;
+ char *flags,*date,buf[BUFLEN],tmp[MAILTMPLEN],file[MAILTMPLEN],
+ lock[MAILTMPLEN];
+ struct utimbuf times;
+ FILE *sf,*df;
+ MESSAGECACHE elt;
+ STRING *message;
+ unsigned long uidlocation = 0;
+ appenduid_t au = (appenduid_t)
+ (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ? NIL :
+ mail_parameters (NIL,GET_APPENDUID,NIL));
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ long ret = LONGT;
+ MAILSTREAM *tstream = NIL;
+ if (!stream) { /* stream specified? */
+ stream = &unixproto; /* no, default stream to prototype */
+ for (i = 0; i < NUSERFLAGS && stream->user_flags[i]; ++i)
+ fs_give ((void **) &stream->user_flags[i]);
+ }
+ if (!unix_valid (mailbox)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!compare_cstring (mailbox,"INBOX")) {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ unix_create (NIL,"INBOX"); /* create empty INBOX */
+ case 0: /* merely empty file? */
+ tstream = stream;
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid UNIX-format mailbox name: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a UNIX-format mailbox: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get sniffing stream for keywords */
+ else if (!(tstream = mail_open (NIL,mailbox,
+ OP_READONLY|OP_SILENT|OP_NOKOD|OP_SNIFF))) {
+ sprintf (tmp,"Unable to examine mailbox for APPEND: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+
+ /* get first message */
+ if (!(*af) (tstream,data,&flags,&date,&message)) return NIL;
+ if (!(sf = tmpfile ())) { /* must have scratch file */
+ sprintf (tmp,".%lx.%lx",(unsigned long) time (0),(unsigned long)getpid ());
+ if (!stat (tmp,&sbuf) || !(sf = fopen (tmp,"wb+"))) {
+ sprintf (tmp,"Unable to create scratch file: %.80s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ unlink (tmp);
+ }
+ do { /* parse date */
+ if (!date) rfc822_date (date = tmp);
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ mm_log (tmp,ERROR);
+ }
+ else { /* user wants to suppress time zones? */
+ if (mail_parameters (NIL,GET_NOTIMEZONES,NIL)) {
+ time_t when = mail_longdate (&elt);
+ date = ctime (&when); /* use traditional date */
+ }
+ /* use POSIX-style date */
+ else date = mail_cdate (tmp,&elt);
+ if (!SIZE (message)) mm_log ("Append of zero-length message",ERROR);
+ else if (!unix_collect_msg (tstream,sf,flags,date,message)) {
+ sprintf (tmp,"Error writing scratch file: %.80s",strerror (errno));
+ mm_log (tmp,ERROR);
+ }
+ /* get next message */
+ else if ((*af) (tstream,data,&flags,&date,&message)) continue;
+ }
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ } while (message); /* until no more messages */
+ if (fflush (sf)) {
+ sprintf (tmp,"Error finishing scratch file: %.80s",strerror (errno));
+ mm_log (tmp,ERROR);
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ }
+ i = ftell (sf); /* size of scratch file */
+
+ /* close sniffing stream */
+ if (tstream != stream) tstream = mail_close (tstream);
+ mm_critical (stream); /* go critical */
+ /* try to open readwrite for UIDPLUS */
+ if ((tstream = mail_open_work (&unixdriver,NIL,mailbox,
+ OP_SILENT|OP_NOKOD)) && tstream->rdonly)
+ tstream = mail_close (tstream);
+ if (au && !tstream) { /* wanted an APPENDUID? */
+ sprintf (tmp,"Unable to re-open mailbox for APPENDUID: %.80s",mailbox);
+ MM_LOG (tmp,WARN);
+ au = NIL;
+ }
+ if (((fd = unix_lock (dummy_file (file,mailbox),
+ O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IREAD|S_IWRITE,
+ lock,LOCK_EX)) < 0) || !(df = fdopen (fd,"ab"))) {
+ mm_nocritical (stream); /* done with critical */
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ rewind (sf);
+ times.modtime = time (0); /* set mtime to now */
+ /* write all messages */
+ if (!unix_append_msgs (tstream,sf,df,au ? dst : NIL) ||
+ (fflush (df) == EOF) || fsync (fd)) {
+ sprintf (buf,"Message append failed: %s",strerror (errno));
+ mm_log (buf,ERROR);
+ ftruncate (fd,sbuf.st_size);/* revert file */
+ times.actime = /* preserve \Marked status */
+ ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
+ sbuf.st_atime : times.modtime;
+ ret = NIL; /* return error */
+ }
+ /* set atime to now-1 if successful copy */
+ else times.actime = times.modtime - 1;
+ utime (file,&times); /* set the times */
+ fclose (sf); /* done with scratch file */
+ /* force UIDVALIDITY assignment now */
+ if (tstream && !tstream->uid_validity)
+ tstream->uid_validity = (unsigned long) time (0);
+ /* return sets if doing APPENDUID */
+ if (au && ret) (*au) (mailbox,tstream->uid_validity,dst);
+ else mail_free_searchset (&dst);
+ flock (fd,LOCK_UN); /* unlock mailbox (can't use unix_unlock() */
+ if (lock && *lock) unlink (lock);
+ fclose (df); /* close mailbox */
+ if (tstream) { /* update last UID if we can */
+ UNIXLOCAL * local = (UNIXLOCAL *) tstream->local;
+ local->dirty = T; /* do a rewrite */
+ local->appending = T; /* but not at the cost of marking as old */
+ tstream = mail_close (tstream);
+ }
+ mm_nocritical (stream); /* release critical */
+ return ret;
+}
+
+/* Collect and write single message to append scratch file
+ * Accepts: MAIL stream
+ * scratch file
+ * flags
+ * date
+ * message stringstruct
+ * Returns: NIL if write error, else T
+ */
+
+int unix_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg)
+{
+ unsigned char *s,*t;
+ unsigned long uf;
+ long f = mail_parse_flags (stream,flags,&uf);
+ /* write metadata */
+ if (fprintf (sf,"%ld %lu ",f,SIZE (msg) + 2) < 0) return NIL;
+ for (s = date; *s; *s++) switch (*s) {
+ default:
+ if (putc (*s,sf) == EOF) return NIL;
+ case '\r': case '\n':
+ break;
+ }
+ if (fputs ("\r\n",sf) == EOF) return NIL;
+ while (uf) /* write user flags */
+ if ((s = stream->user_flags[find_rightmost_bit (&uf)]) &&
+ (fprintf (sf," %s",s) < 0)) return NIL;
+ if (fputs ("\r\n",sf) == EOF) return NIL;
+ while (SIZE (msg)) { /* copy text to scratch file */
+ for (s = (unsigned char *) msg->curpos, t = s + msg->cursize; s < t; ++s)
+ if (!*s) *s = 0x80; /* disallow NUL */
+ /* write buffered text */
+ if (fwrite (msg->curpos,1,msg->cursize,sf) == msg->cursize)
+ SETPOS (msg,GETPOS (msg) + msg->cursize);
+ else return NIL; /* failed */
+ }
+ /* write trailing CRLF and return */
+ return (fputs ("\r\n",sf) == EOF) ? NIL : T;
+}
+
+/* Append messages from scratch file to mailbox
+ * Accepts: MAIL stream
+ * source file
+ * destination file
+ * uidset to update if non-NIL
+ * Returns: T if success, NIL if failure
+ */
+
+int unix_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set)
+{
+ int ti,zn,c;
+ long f;
+ unsigned long i,j;
+ char *x,tmp[MAILTMPLEN];
+ int hdrp = T;
+ /* get message metadata line */
+ while (fgets (tmp,MAILTMPLEN,sf)) {
+ if (!(isdigit (tmp[0]) && strchr (tmp,'\n'))) return NIL;
+ f = strtol (tmp,&x,10); /* get flags */
+ if (!((*x++ == ' ') && isdigit (*x))) return NIL;
+ i = strtoul (x,&x,10); /* get message size */
+ if ((*x++ != ' ') || /* build initial header */
+ (fprintf (df,"From %s@%s %sStatus: ",myusername(),mylocalhost(),x)<0)||
+ (f&fSEEN && (putc ('R',df) == EOF)) ||
+ (fputs ("\r\nX-Status: ",df) == EOF) ||
+ (f&fDELETED && (putc ('D',df) == EOF)) ||
+ (f&fFLAGGED && (putc ('F',df) == EOF)) ||
+ (f&fANSWERED && (putc ('A',df) == EOF)) ||
+ (f&fDRAFT && (putc ('T',df) == EOF)) ||
+ (fputs ("\r\nX-Keywords:",df) == EOF)) return NIL;
+ /* copy keywords */
+ while ((c = getc (sf)) != '\n') switch (c) {
+ case EOF:
+ return NIL;
+ default:
+ if (putc (c,df) == EOF) return NIL;
+ }
+ if ((putc ('\n',df) == EOF) ||
+ (set && (fprintf (df,"X-UID: %lu\r\n",++(stream->uid_last)) < 0)))
+ return NIL;
+
+ for (c = '\n'; i && fgets (tmp,MAILTMPLEN,sf); c = tmp[j-1]) {
+ /* get read line length */
+ if (i < (j = strlen (tmp))) fatal ("unix_append_msgs overrun");
+ i -= j; /* number of bytes left */
+ if (!j) continue; /* do nothing if line emptied */
+ /* complete line? */
+ if ((c == '\n')) switch (tmp[0]) {
+ case 'F': /* possible "From " (case counts here) */
+ if ((j > 4) && (tmp[0] == 'F') && (tmp[1] == 'r') && (tmp[2] == 'o') &&
+ (tmp[3] == 'm') && (tmp[4] == ' ')) {
+ if (!unix_fromwidget) {
+ VALID (tmp,x,ti,zn);/* conditional, only write widget if */
+ if (!ti) break; /* it looks like a valid header */
+ } /* write the widget */
+ if (putc ('>',df) == EOF) return NIL;
+ }
+ break;
+ case 'S': case 's': /* possible "Status:" */
+ if (hdrp && (j > 6) && ((tmp[1] == 't') || (tmp[1] == 'T')) &&
+ ((tmp[2] == 'a') || (tmp[2] == 'A')) &&
+ ((tmp[3] == 't') || (tmp[3] == 'T')) &&
+ ((tmp[4] == 'u') || (tmp[4] == 'U')) &&
+ ((tmp[5] == 's') || (tmp[5] == 'S')) && (tmp[6] == ':') &&
+ (fputs ("X-Original-",df) == EOF)) return NIL;
+ break;
+ case 'X': case 'x': /* possible X-??? header */
+ if (hdrp && (tmp[1] == '-') &&
+ /* possible X-UID: */
+ (((j > 5) && ((tmp[2] == 'U') || (tmp[2] == 'u')) &&
+ ((tmp[3] == 'I') || (tmp[3] == 'i')) &&
+ ((tmp[4] == 'D') || (tmp[4] == 'd')) && (tmp[5] == ':')) ||
+ /* possible X-IMAP: */
+ ((j > 6) && ((tmp[2] == 'I') || (tmp[2] == 'i')) &&
+ ((tmp[3] == 'M') || (tmp[3] == 'm')) &&
+ ((tmp[4] == 'A') || (tmp[4] == 'a')) &&
+ ((tmp[5] == 'P') || (tmp[5] == 'p')) &&
+ ((tmp[6] == ':') ||
+ /* or X-IMAPbase: */
+ ((j > 10) && ((tmp[6] == 'b') || (tmp[6] == 'B')) &&
+ ((tmp[7] == 'a') || (tmp[7] == 'A')) &&
+ ((tmp[8] == 's') || (tmp[8] == 'S')) &&
+ ((tmp[9] == 'e') || (tmp[9] == 'E')) && (tmp[10] == ':')))) ||
+ /* possible X-Status: */
+ ((j > 8) && ((tmp[2] == 'S') || (tmp[2] == 's')) &&
+ ((tmp[3] == 't') || (tmp[3] == 'T')) &&
+ ((tmp[4] == 'a') || (tmp[4] == 'A')) &&
+ ((tmp[5] == 't') || (tmp[5] == 'T')) &&
+ ((tmp[6] == 'u') || (tmp[6] == 'U')) &&
+ ((tmp[7] == 's') || (tmp[7] == 'S')) && (tmp[8] == ':')) ||
+ /* possible X-Keywords: */
+ ((j > 10) && ((tmp[2] == 'K') || (tmp[2] == 'k')) &&
+ ((tmp[3] == 'e') || (tmp[3] == 'E')) &&
+ ((tmp[4] == 'y') || (tmp[4] == 'Y')) &&
+ ((tmp[5] == 'w') || (tmp[5] == 'W')) &&
+ ((tmp[6] == 'o') || (tmp[6] == 'O')) &&
+ ((tmp[7] == 'r') || (tmp[7] == 'R')) &&
+ ((tmp[8] == 'd') || (tmp[8] == 'D')) &&
+ ((tmp[9] == 's') || (tmp[9] == 'S')) && (tmp[10] == ':'))) &&
+ (fputs ("X-Original-",df) == EOF)) return NIL;
+ break;
+ case '\n': /* blank line */
+ hdrp = NIL;
+ break;
+ default: /* nothing to do */
+ break;
+ }
+ /* just write the line */
+ if (fwrite (tmp,1,j,df) != j) return NIL;
+ }
+ if (i) return NIL; /* didn't read entire message */
+ /* update set */
+ if (stream) mail_append_set (set,stream->uid_last);
+ }
+ return T;
+}
+
+/* Internal routines */
+
+
+/* UNIX mail abort stream
+ * Accepts: MAIL stream
+ */
+
+void unix_abort (MAILSTREAM *stream)
+{
+ if (LOCAL) { /* only if a file is open */
+ if (LOCAL->fd >= 0) close (LOCAL->fd);
+ if (LOCAL->ld >= 0) { /* have a mailbox lock? */
+ flock (LOCAL->ld,LOCK_UN);/* yes, release the lock */
+ close (LOCAL->ld); /* close the lock file */
+ unlink (LOCAL->lname); /* and delete it */
+ }
+ if (LOCAL->lname) fs_give ((void **) &LOCAL->lname);
+ /* free local text buffers */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data);
+ if (LOCAL->linebuf) fs_give ((void **) &LOCAL->linebuf);
+ if (LOCAL->line) fs_give ((void **) &LOCAL->line);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* UNIX open and lock mailbox
+ * Accepts: file name to open/lock
+ * file open mode
+ * destination buffer for lock file name
+ * type of locking operation (LOCK_SH or LOCK_EX)
+ */
+
+int unix_lock (char *file,int flags,int mode,char *lock,int op)
+{
+ int fd,ld,j;
+ int i = LOCKTIMEOUT * 60 - 1;
+ char tmp[MAILTMPLEN];
+ time_t t;
+ struct stat sb;
+ sprintf (lock,"%s.lock",file);/* build lock filename */
+ do { /* until OK or out of tries */
+ t = time (0); /* get the time now */
+ /* try to get the lock */
+ if ((ld = open(lock,O_BINARY|O_WRONLY|O_CREAT|O_EXCL,S_IREAD|S_IWRITE))>=0)
+ close (ld); /* got it, close the lock file! */
+ else if (errno != EEXIST) { /* miscellaneous error */
+ sprintf (tmp,"Error creating %.80s: %s",lock,strerror (errno));
+ if (!(i%15)) mm_log (tmp,WARN);
+ }
+ /* lock exists, still active? */
+ else if (!stat (lock,&sb) && (t > sb.st_ctime + LOCKTIMEOUT * 60) &&
+ ((ld = open(lock,O_BINARY|O_WRONLY|O_CREAT,S_IREAD|S_IWRITE))>=0))
+ close (ld); /* got timed-out lock file */
+ else { /* active lock, try again */
+ if (!(i%15)) {
+ sprintf (tmp,"Mailbox %.80s is locked, will override in %d seconds...",
+ file,i);
+ mm_log (tmp,WARN);
+ }
+ sleep (1); /* wait a second before next retry */
+ }
+ } while (*lock && ld < 0 && i--);
+ /* open file */
+ if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
+ else { /* open failed */
+ j = errno; /* preserve error code */
+ if (*lock) unlink (lock); /* flush the lock file if any */
+ errno = j; /* restore error code */
+ }
+ return fd;
+}
+
+/* UNIX unlock and close mailbox
+ * Accepts: file descriptor
+ * (optional) mailbox stream to check atime/mtime
+ * (optional) lock file name
+ */
+
+void unix_unlock (int fd,MAILSTREAM *stream,char *lock)
+{
+ if (stream) { /* need to muck with times? */
+ struct stat sbuf;
+ struct utimbuf times;
+ time_t now = time (0);
+ fstat (fd,&sbuf); /* get file times */
+ if (LOCAL->ld >= 0) { /* yes, readwrite session? */
+ times.actime = now; /* set atime to now */
+ /* set mtime to (now - 1) if necessary */
+ times.modtime = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
+ }
+ else if (stream->recent) { /* readonly with recent messages */
+ if ((sbuf.st_atime >= sbuf.st_mtime) ||
+ (sbuf.st_atime >= sbuf.st_ctime))
+ /* keep past mtime, whack back atime */
+ times.actime = (times.modtime = (sbuf.st_mtime < now) ?
+ sbuf.st_mtime : now) - 1;
+ else now = 0; /* no time change needed */
+ }
+ /* readonly with no recent messages */
+ else if ((sbuf.st_atime < sbuf.st_mtime) ||
+ (sbuf.st_atime < sbuf.st_ctime)) {
+ times.actime = now; /* set atime to now */
+ /* set mtime to (now - 1) if necessary */
+ times.modtime = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
+ }
+ else now = 0; /* no time change needed */
+ /* set the times, note change */
+ if (now && !utime (stream->mailbox,&times))
+ LOCAL->filetime = times.modtime;
+ }
+ flock (fd,LOCK_UN); /* release flock'ers */
+ if (!stream) close (fd); /* close the file if no stream */
+ /* flush the lock file if any */
+ if (lock && *lock) unlink (lock);
+}
+
+/* UNIX mail parse and lock mailbox
+ * Accepts: MAIL stream
+ * space to write lock file name
+ * type of locking operation
+ * Returns: T if parse OK, critical & mailbox is locked shared; NIL if failure
+ */
+
+int unix_parse (MAILSTREAM *stream,char *lock,int op)
+{
+ int zn;
+ unsigned long i,j,k,m;
+ unsigned char c,*s,*t,*u,tmp[MAILTMPLEN],date[30];
+ int ti = 0,retain = T;
+ unsigned long nmsgs = stream->nmsgs;
+ unsigned long prevuid = nmsgs ? mail_elt (stream,nmsgs)->private.uid : 0;
+ unsigned long recent = stream->recent;
+ unsigned long oldnmsgs = stream->nmsgs;
+ short silent = stream->silent;
+ short pseudoseen = NIL;
+ struct stat sbuf;
+ STRING bs;
+ FDDATA d;
+ MESSAGECACHE *elt;
+ mail_lock (stream); /* guard against recursion or pingers */
+ /* toss out previous descriptor */
+ if (LOCAL->fd >= 0) close (LOCAL->fd);
+ mm_critical (stream); /* open and lock mailbox (shared OK) */
+ if ((LOCAL->fd = unix_lock (stream->mailbox,
+ O_BINARY + ((LOCAL->ld >= 0) ? O_RDWR:O_RDONLY),
+ NIL,lock,op)) < 0) {
+ sprintf (tmp,"Mailbox open failed, aborted: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ unix_abort (stream);
+ mail_unlock (stream);
+ mm_nocritical (stream); /* done with critical */
+ return NIL;
+ }
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* validate change in size */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (tmp,"Mailbox shrank from %lu to %lu bytes, aborted",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ mm_log (tmp,ERROR); /* this is pretty bad */
+ unix_unlock (LOCAL->fd,stream,lock);
+ unix_abort (stream);
+ mail_unlock (stream);
+ mm_nocritical (stream); /* done with critical */
+ return NIL;
+ }
+
+ /* new data? */
+ else if (i = sbuf.st_size - LOCAL->filesize) {
+ d.fd = LOCAL->fd; /* yes, set up file descriptor */
+ d.pos = LOCAL->filesize; /* get to that position in the file */
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* file chunk size */
+ INIT (&bs,fd_string,&d,i); /* initialize stringstruct */
+ /* skip leading whitespace for broken MTAs */
+ while (((c = CHR (&bs)) == '\n') || (c == '\r') ||
+ (c == ' ') || (c == '\t')) SNX (&bs);
+ if (SIZE (&bs)) { /* read new data */
+ /* remember internal header position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ s = unix_mbxline (stream,&bs,&i);
+ t = NIL,zn = 0;
+ if (i) VALID (s,t,ti,zn); /* see if valid From line */
+ if (!ti) { /* someone pulled the rug from under us */
+ sprintf (tmp,"Unexpected changes to mailbox (try restarting): %.20s",
+ (char *) s);
+ mm_log (tmp,ERROR);
+ unix_unlock (LOCAL->fd,stream,lock);
+ unix_abort (stream);
+ mail_unlock (stream);
+ mm_nocritical (stream); /* done with critical */
+ return NIL;
+ }
+ stream->silent = T; /* quell main program new message events */
+ do { /* found a message */
+ /* instantiate first new message */
+ mail_exists (stream,++nmsgs);
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ recent++; /* assume recent by default */
+ elt->recent = T;
+ /* note position/size of internal header */
+ elt->private.special.offset = j;
+ elt->private.msg.header.offset = elt->private.special.text.size = i;
+
+ /* generate plausible IMAPish date string */
+ date[2] = date[6] = date[20] = '-'; date[11] = ' ';
+ date[14] = date[17] = ':';
+ /* dd */
+ date[0] = t[ti - 2]; date[1] = t[ti - 1];
+ /* mmm */
+ date[3] = t[ti - 6]; date[4] = t[ti - 5]; date[5] = t[ti - 4];
+ /* hh */
+ date[12] = t[ti + 1]; date[13] = t[ti + 2];
+ /* mm */
+ date[15] = t[ti + 4]; date[16] = t[ti + 5];
+ if (t[ti += 6] == ':') {/* ss */
+ date[18] = t[++ti]; date[19] = t[++ti];
+ ti++; /* move to space */
+ }
+ else date[18] = date[19] = '0';
+ /* yy -- advance over timezone if necessary */
+ if (zn == ti) ti += (((t[zn+1] == '+') || (t[zn+1] == '-')) ? 6 : 4);
+ date[7] = t[ti + 1]; date[8] = t[ti + 2];
+ date[9] = t[ti + 3]; date[10] = t[ti + 4];
+ /* zzz */
+ t = zn ? (t + zn + 1) : (unsigned char *) "LCL";
+ date[21] = *t++; date[22] = *t++; date[23] = *t++;
+ if ((date[21] != '+') && (date[21] != '-')) date[24] = '\0';
+ else { /* numeric time zone */
+ date[24] = *t++; date[25] = *t++;
+ date[26] = '\0'; date[20] = ' ';
+ }
+ /* set internal date */
+ if (!mail_parse_date (elt,date)) {
+ sprintf (tmp,"Unable to parse internal date: %s",(char *) date);
+ mm_log (tmp,WARN);
+ }
+
+ do { /* look for message body */
+ s = t = unix_mbxline (stream,&bs,&i);
+ if (i) switch (*s) { /* check header lines */
+ case 'X': /* possible X-???: line */
+ if (s[1] == '-') { /* must be immediately followed by hyphen */
+ /* X-Status: becomes Status: in S case */
+ if (s[2] == 'S' && s[3] == 't' && s[4] == 'a' && s[5] == 't' &&
+ s[6] == 'u' && s[7] == 's' && s[8] == ':') s += 2;
+ /* possible X-Keywords */
+ else if (s[2] == 'K' && s[3] == 'e' && s[4] == 'y' &&
+ s[5] == 'w' && s[6] == 'o' && s[7] == 'r' &&
+ s[8] == 'd' && s[9] == 's' && s[10] == ':') {
+ SIZEDTEXT uf;
+ retain = NIL; /* don't retain continuation */
+ s += 11; /* flush leading whitespace */
+ while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n'))){
+ while (*s == ' ') s++;
+ /* find end of keyword */
+ if (!(u = strpbrk (s," \n\r"))) u = s + strlen (s);
+ /* got a keyword? */
+ if ((k = (u - s)) && (k <= MAXUSERFLAG)) {
+ uf.data = (unsigned char *) s;
+ uf.size = k;
+ for (j = 0; (j < NUSERFLAGS) && stream->user_flags[j]; ++j)
+ if (!compare_csizedtext (stream->user_flags[j],&uf)) {
+ elt->user_flags |= ((long) 1) << j;
+ break;
+ }
+ }
+ s = u; /* advance to next keyword */
+ }
+ break;
+ }
+
+ /* possible X-IMAP */
+ else if ((s[2] == 'I') && (s[3] == 'M') && (s[4] == 'A') &&
+ (s[5] == 'P') && ((m = (s[6] == ':')) ||
+ ((s[6] == 'b') && (s[7] == 'a') &&
+ (s[8] == 's') && (s[9] == 'e') &&
+ (s[10] == ':')))) {
+ retain = NIL; /* don't retain continuation */
+ if ((nmsgs == 1) && !stream->uid_validity) {
+ /* advance to data */
+ s += m ? 7 : 11;
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ j = 0; /* slurp UID validity */
+ /* found a digit? */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ /* must have valid UID validity and UID last */
+ if (j && isdigit (*s)) {
+ /* pseudo-header seen if X-IMAP */
+ if (m) pseudoseen = LOCAL->pseudo = T;
+ /* save UID validity */
+ stream->uid_validity = j;
+ j = 0; /* slurp UID last */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* save UID last */
+ stream->uid_last = j;
+ /* process keywords */
+ for (j = 0; (*s != '\n') && ((*s != '\r')||(s[1] != '\n'));
+ s = u,j++) {
+ /* flush leading whitespace */
+ while (*s == ' ') s++;
+ u = strpbrk (s," \n\r");
+ /* got a keyword? */
+ if ((j < NUSERFLAGS) && (k = (u - s)) &&
+ (k <= MAXUSERFLAG)) {
+ if (stream->user_flags[j])
+ fs_give ((void **) &stream->user_flags[j]);
+ stream->user_flags[j] = (char *) fs_get (k + 1);
+ strncpy (stream->user_flags[j],s,k);
+ stream->user_flags[j][k] = '\0';
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ /* possible X-UID */
+ else if (s[2] == 'U' && s[3] == 'I' && s[4] == 'D' &&
+ s[5] == ':') {
+ retain = NIL; /* don't retain continuation */
+ /* only believe if have a UID validity */
+ if (stream->uid_validity && ((nmsgs > 1) || !pseudoseen)) {
+ s += 6; /* advance to UID value */
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ j = 0;
+ /* found a digit? */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* flush remainder of line */
+ while (*s != '\n') s++;
+ /* make sure not duplicated */
+ if (elt->private.uid)
+ sprintf (tmp,"Message %lu UID %lu already has UID %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,elt->private.uid);
+ /* make sure UID doesn't go backwards */
+ else if (j <= prevuid)
+ sprintf (tmp,"Message %lu UID %lu less than %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,prevuid + 1);
+#if 0 /* this is currently broken by UIDPLUS */
+ /* or skip by mailbox's recorded last */
+ else if (j > stream->uid_last)
+ sprintf (tmp,"Message %lu UID %lu greater than last %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,stream->uid_last);
+#endif
+ else { /* normal UID case */
+ prevuid = elt->private.uid = j;
+#if 1 /* temporary kludge for UIDPLUS */
+ if (prevuid > stream->uid_last) {
+ stream->uid_last = prevuid;
+ LOCAL->ddirty = LOCAL->dirty = T;
+ }
+#endif
+ break; /* exit this cruft */
+ }
+ mm_log (tmp,WARN);
+ /* invalidate UID validity */
+ stream->uid_validity = 0;
+ elt->private.uid = 0;
+ }
+ break;
+ }
+ }
+ /* otherwise fall into S case */
+
+ case 'S': /* possible Status: line */
+ if (s[0] == 'S' && s[1] == 't' && s[2] == 'a' && s[3] == 't' &&
+ s[4] == 'u' && s[5] == 's' && s[6] == ':') {
+ retain = NIL; /* don't retain continuation */
+ s += 6; /* advance to status flags */
+ do switch (*s++) {/* parse flags */
+ case 'R': /* message read */
+ elt->seen = T;
+ break;
+ case 'O': /* message old */
+ if (elt->recent) {
+ elt->recent = NIL;
+ recent--; /* it really wasn't recent */
+ }
+ break;
+ case 'D': /* message deleted */
+ elt->deleted = T;
+ break;
+ case 'F': /* message flagged */
+ elt->flagged = T;
+ break;
+ case 'A': /* message answered */
+ elt->answered = T;
+ break;
+ case 'T': /* message is a draft */
+ elt->draft = T;
+ break;
+ default: /* some other crap */
+ break;
+ } while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n')));
+ break; /* all done */
+ }
+ /* otherwise fall into default case */
+
+ default: /* ordinary header line */
+ if ((*s == 'S') || (*s == 's') ||
+ (((*s == 'X') || (*s == 'x')) && (s[1] == '-'))) {
+ unsigned char *e,*v;
+ /* must match what mail_filter() does */
+ for (u = s,v = tmp,e = u + min (i,MAILTMPLEN - 1);
+ (u < e) && ((c = (*u ? *u : (*u = ' '))) != ':') &&
+ ((c > ' ') || ((c != ' ') && (c != '\t') &&
+ (c != '\r') && (c != '\n')));
+ *v++ = *u++);
+ *v = '\0'; /* tie off */
+ /* matches internal header? */
+ if (!compare_cstring (tmp,"STATUS") ||
+ !compare_cstring (tmp,"X-STATUS") ||
+ !compare_cstring (tmp,"X-KEYWORDS") ||
+ !compare_cstring (tmp,"X-UID") ||
+ !compare_cstring (tmp,"X-IMAP") ||
+ !compare_cstring (tmp,"X-IMAPBASE")) {
+ char err[MAILTMPLEN];
+ sprintf (err,"Discarding bogus %s header in message %lu",
+ (char *) tmp,elt->msgno);
+ mm_log (err,WARN);
+ retain = NIL; /* don't retain continuation */
+ break; /* different case or something */
+ }
+ }
+ /* retain or non-continuation? */
+ if (retain || ((*s != ' ') && (*s != '\t'))) {
+ retain = T; /* retaining continuation now */
+ /* line length in CRLF format newline */
+ k = i + (((i < 2) || (s[i - 2] != '\r')) ? 1 : 0);
+ /* header size */
+ elt->rfc822_size = elt->private.spare.data += k;
+ }
+ else {
+ char err[MAILTMPLEN];
+ sprintf (err,"Discarding bogus continuation in msg %lu: %.80s",
+ elt->msgno,(char *) s);
+ if (u = strpbrk (err,"\r\n")) *u = '\0';
+ mm_log (err,WARN);
+ break; /* different case or something */
+ }
+ break;
+ }
+ } while (i && (*t != '\n') && ((*t != '\r') || (t[1] != '\n')));
+ /* "internal" header sans trailing newline */
+ if (i) elt->private.spare.data -= 2;
+ /* assign a UID if none found */
+ if (((nmsgs > 1) || !pseudoseen) && !elt->private.uid) {
+ prevuid = elt->private.uid = ++stream->uid_last;
+ elt->private.dirty = T;
+ LOCAL->ddirty = T; /* force update */
+ }
+ else elt->private.dirty = elt->recent;
+
+ /* note size of header, location of text */
+ elt->private.msg.header.text.size =
+ (elt->private.msg.text.offset =
+ (LOCAL->filesize + GETPOS (&bs)) - elt->private.special.offset) -
+ elt->private.special.text.size;
+ k = m = 0; /* no previous line size yet */
+ /* note current position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ if (i) do { /* look for next message */
+ s = unix_mbxline (stream,&bs,&i);
+ if (i) { /* got new data? */
+ VALID (s,t,ti,zn); /* yes, parse line */
+ if (!ti) { /* not a header line, add it to message */
+ if (s[i - 1] == '\n')
+ elt->rfc822_size +=
+ k = i + (m = (((i < 2) || s[i - 2] != '\r') ? 1 : 0));
+ else { /* file does not end with newline! */
+ elt->rfc822_size += i;
+ k = m = 0;
+ }
+ /* update current position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ }
+ }
+ } while (i && !ti); /* until found a header */
+ elt->private.msg.text.text.size = j -
+ (elt->private.special.offset + elt->private.msg.text.offset);
+ if (k == 2) { /* last line was blank? */
+ elt->private.msg.text.text.size -= (m ? 1 : 2);
+ elt->rfc822_size -= 2;
+ }
+ /* until end of buffer */
+ } while (!stream->sniff && i);
+ if (pseudoseen) { /* flush pseudo-message if present */
+ /* decrement recent count */
+ if (mail_elt (stream,1)->recent) recent--;
+ /* and the exists count */
+ mail_exists (stream,nmsgs--);
+ mail_expunged(stream,1);/* fake an expunge of that message */
+ }
+ /* need to start a new UID validity? */
+ if (!stream->uid_validity) {
+ stream->uid_validity = (unsigned long) time (0);
+ if (nmsgs) { /* don't bother if empty file */
+ /* make dirty to restart UID epoch */
+ LOCAL->ddirty = LOCAL->dirty = T;
+ /* need to rewrite msg 1 if not pseudo */
+ if (!LOCAL->pseudo) mail_elt (stream,1)->private.dirty = T;
+ mm_log ("Assigning new unique identifiers to all messages",NIL);
+ }
+ }
+ stream->nmsgs = oldnmsgs; /* whack it back down */
+ stream->silent = silent; /* restore old silent setting */
+ /* notify upper level of new mailbox sizes */
+ mail_exists (stream,nmsgs);
+ mail_recent (stream,recent);
+ /* mark dirty so O flags are set */
+ if (recent) LOCAL->dirty = T;
+ }
+ }
+ /* no change, don't babble if never got time */
+ else if (LOCAL->filetime && LOCAL->filetime != sbuf.st_mtime)
+ mm_log ("New mailbox modification time but apparently no changes",WARN);
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ LOCAL->filetime = sbuf.st_mtime;
+ return T; /* return the winnage */
+}
+
+/* UNIX read line from mailbox
+ * Accepts: mail stream
+ * stringstruct
+ * pointer to line size
+ * Returns: pointer to input line
+ */
+
+char *unix_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size)
+{
+ unsigned long i,j,k,m;
+ char *s,*t,*te;
+ char *ret = "";
+ /* flush old buffer */
+ if (LOCAL->line) fs_give ((void **) &LOCAL->line);
+ /* if buffer needs refreshing */
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ if (SIZE (bs)) { /* find newline */
+ /* end of fast scan */
+ te = (t = (s = bs->curpos) + bs->cursize) - 12;
+ while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
+ --s; /* back up */
+ break; /* exit loop */
+ }
+ /* final character-at-a-time scan */
+ while ((s < t) && (*s != '\n')) ++s;
+ /* difficult case if line spans buffer */
+ if ((i = s - bs->curpos) == bs->cursize) {
+ /* have space in line buffer? */
+ if (i > LOCAL->linebuflen) {
+ fs_give ((void **) &LOCAL->linebuf);
+ LOCAL->linebuf = (char *) fs_get (LOCAL->linebuflen = i);
+ }
+ /* remember what we have so far */
+ memcpy (LOCAL->linebuf,bs->curpos,i);
+ /* load next buffer */
+ SETPOS (bs,k = GETPOS (bs) + i);
+ /* end of fast scan */
+ te = (t = (s = bs->curpos) + bs->cursize) - 12;
+ /* fast scan in overlap buffer */
+ while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
+ --s; /* back up */
+ break; /* exit loop */
+ }
+
+ /* final character-at-a-time scan */
+ while ((s < t) && (*s != '\n')) ++s;
+ /* huge line? */
+ if ((j = s - bs->curpos) == bs->cursize) {
+ SETPOS (bs,GETPOS (bs) + j);
+ /* look for end of line (s-l-o-w!!) */
+ for (m = SIZE (bs); m && (SNX (bs) != '\n'); --m,++j);
+ SETPOS (bs,k); /* go back to where it started */
+ }
+ /* got size of data, make buffer for return */
+ ret = LOCAL->line = (char *) fs_get (i + j + 2);
+ /* copy first chunk */
+ memcpy (ret,LOCAL->linebuf,i);
+ while (j) { /* copy remainder */
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ memcpy (ret + i,bs->curpos,k = min (j,bs->cursize));
+ i += k; /* account for this much read in */
+ j -= k;
+ bs->curpos += k; /* increment new position */
+ bs->cursize -= k; /* eat that many bytes */
+ }
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ /* read newline at end */
+ if (SIZE (bs)) ret[i++] = SNX (bs);
+ ret[i] = '\0'; /* makes debugging easier */
+ }
+ else { /* this is easy */
+ ret = bs->curpos; /* string it at this position */
+ bs->curpos += ++i; /* increment new position */
+ bs->cursize -= i; /* eat that many bytes */
+ }
+ *size = i; /* return that to user */
+ }
+ else *size = 0; /* end of data, return empty */
+ return ret;
+}
+
+/* UNIX make pseudo-header
+ * Accepts: MAIL stream
+ * buffer to write pseudo-header
+ * Returns: length of pseudo-header
+ */
+
+unsigned long unix_pseudo (MAILSTREAM *stream,char *hdr)
+{
+ int i;
+ char *s,*t,tmp[MAILTMPLEN];
+ time_t now = time(0);
+ rfc822_fixed_date (tmp);
+ sprintf (hdr,"From %s %.24s\r\nDate: %s\r\nFrom: %s <%s@%.80s>\r\nSubject: %s\r\nMessage-ID: <%lu@%.80s>\r\nX-IMAP: %010ld %010ld",
+ pseudo_from,ctime (&now),
+ tmp,pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
+ (unsigned long) now,mylocalhost (),stream->uid_validity,
+ stream->uid_last);
+ for (t = hdr + strlen (hdr),i = 0; i < NUSERFLAGS; ++i)
+ if (stream->user_flags[i])
+ sprintf (t += strlen (t)," %s",stream->user_flags[i]);
+ strcpy (t += strlen (t),"\r\nStatus: RO\r\n\r\n");
+ for (s = pseudo_msg,t += strlen (t); *s; *t++ = *s++)
+ if (*s == '\n') *t++ = '\r';
+ *t++ = '\r'; *t++ = '\n'; *t++ = '\r'; *t++ = '\n';
+ *t = '\0'; /* tie off pseudo header */
+ return t - hdr; /* return length of pseudo header */
+}
+
+/* UNIX make status string
+ * Accepts: MAIL stream
+ * destination string to write
+ * message cache entry
+ * UID to write if non-zero (else use elt->private.uid)
+ * non-zero flag to write UID (.LT. 0 to write UID base info too)
+ * Returns: length of string
+ */
+
+unsigned long unix_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
+ unsigned long uid,long flag)
+{
+ char *t,stack[64];
+ char *s = status;
+ unsigned long n;
+ unsigned long pad = 50;
+ /* This used to use sprintf(), but thanks to certain cretinous C libraries
+ with horribly slow implementations of sprintf() I had to change it to this
+ mess. At least it should be fast. */
+ *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't'; *s++ = 'u'; *s++ = 's';
+ *s++ = ':'; *s++ = ' ';
+ if (elt->seen) *s++ = 'R';
+ /* only write O if have a UID */
+ if (flag && (!elt->recent || LOCAL->appending)) *s++ = 'O';
+ *s++ = '\r'; *s++ = '\n';
+ *s++ = 'X'; *s++ = '-'; *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't';
+ *s++ = 'u'; *s++ = 's'; *s++ = ':'; *s++ = ' ';
+ if (elt->deleted) *s++ = 'D';
+ if (elt->flagged) *s++ = 'F';
+ if (elt->answered) *s++ = 'A';
+ if (elt->draft) *s++ = 'T';
+ *s++ = '\r'; *s++ = '\n';
+
+ *s++ = 'X'; *s++ = '-'; *s++ = 'K'; *s++ = 'e'; *s++ = 'y'; *s++ = 'w';
+ *s++ = 'o'; *s++ = 'r'; *s++ = 'd'; *s++ = 's'; *s++ = ':';
+ if (n = elt->user_flags) do {
+ *s++ = ' ';
+ for (t = stream->user_flags[find_rightmost_bit (&n)]; *t; *s++ = *t++);
+ } while (n);
+ n = s - status; /* get size of stuff so far */
+ /* pad X-Keywords to make size constant */
+ if (n < pad) for (n = pad - n; n > 0; --n) *s++ = ' ';
+ *s++ = '\r'; *s++ = '\n';
+ if (flag) { /* want to include UID? */
+ t = stack;
+ /* push UID digits on the stack */
+ n = uid ? uid : elt->private.uid;
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ *s++ = 'X'; *s++ = '-'; *s++ = 'U'; *s++ = 'I'; *s++ = 'D'; *s++ = ':';
+ *s++ = ' ';
+ /* pop UID from stack */
+ while (t > stack) *s++ = *--t;
+ *s++ = '\r'; *s++ = '\n';
+ }
+ /* end of extended message status */
+ *s++ = '\r'; *s++ = '\n'; *s = '\0';
+ return s - status; /* return size of resulting string */
+}
+
+/* Rewrite mailbox file
+ * Accepts: MAIL stream, must be critical and locked
+ * return pointer to number of expunged messages if want expunge
+ * lock file name
+ * expunge sequence, not deleted flag
+ * Returns: T if success and mailbox unlocked, NIL if failure
+ */
+
+#define OVERFLOWBUFLEN 8192 /* initial overflow buffer length */
+
+long unix_rewrite (MAILSTREAM *stream,unsigned long *nexp,char *lock,
+ long flags)
+{
+ MESSAGECACHE *elt;
+ UNIXFILE f;
+ char *s;
+ struct utimbuf times;
+ long ret,flag;
+ unsigned long i,j;
+ unsigned long recent = stream->recent;
+ unsigned long size = LOCAL->pseudo ? unix_pseudo (stream,LOCAL->buf) : 0;
+ if (nexp) *nexp = 0; /* initially nothing expunged */
+ /* calculate size of mailbox after rewrite */
+ for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs; i++) {
+ elt = mail_elt (stream,i); /* get cache */
+ if (!(nexp && elt->deleted && (flags ? elt->sequence : T))) {
+ /* add RFC822 size of this message */
+ size += elt->private.special.text.size + elt->private.spare.data +
+ unix_xstatus (stream,LOCAL->buf,elt,NIL,flag) +
+ elt->private.msg.text.text.size + 2;
+ flag = 1; /* only count X-IMAPbase once */
+ }
+ }
+ if (!size) { /* no messages and no pseudo, make one now */
+ size = unix_pseudo (stream,LOCAL->buf);
+ LOCAL->pseudo = T;
+ }
+ /* extend the file as necessary */
+ if (ret = unix_extend (stream,size)) {
+ /* Set up buffered I/O file structure
+ * curpos current position being written through buffering
+ * filepos current position being written physically to the disk
+ * bufpos current position being written in the buffer
+ * protect current maximum position that can be written to the disk
+ * before buffering is forced
+ * The code tries to buffer so that that disk is written in multiples of
+ * OVERBLOWBUFLEN bytes.
+ */
+ f.stream = stream; /* note mail stream */
+ f.curpos = f.filepos = 0; /* start of file */
+ f.protect = stream->nmsgs ? /* initial protection pointer */
+ mail_elt (stream,1)->private.special.offset : 8192;
+ f.bufpos = f.buf = (char *) fs_get (f.buflen = OVERFLOWBUFLEN);
+
+ if (LOCAL->pseudo) /* update pseudo-header */
+ unix_write (&f,LOCAL->buf,unix_pseudo (stream,LOCAL->buf));
+ /* loop through all messages */
+ for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs;) {
+ elt = mail_elt (stream,i);/* get cache */
+ /* expunge this message? */
+ if (nexp && elt->deleted && (flags ? elt->sequence : T)) {
+ /* one less recent message */
+ if (elt->recent) --recent;
+ mail_expunged(stream,i);/* notify upper levels */
+ ++*nexp; /* count up one more expunged message */
+ }
+ else { /* preserve this message */
+ i++; /* advance to next message */
+ if ((flag < 0) || /* need to rewrite message? */
+ elt->private.dirty ||
+ (((unsigned long) f.curpos) != elt->private.special.offset) ||
+ (elt->private.msg.header.text.size !=
+ (elt->private.spare.data +
+ unix_xstatus (stream,LOCAL->buf,elt,NIL,flag)))) {
+ unsigned long newoffset = f.curpos;
+ /* yes, seek to internal header */
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
+ /* protection pointer moves to RFC822 header */
+ f.protect = elt->private.special.offset +
+ elt->private.msg.header.offset;
+ /* write internal header */
+ unix_write (&f,LOCAL->buf,elt->private.special.text.size);
+ /* get RFC822 header */
+ s = unix_header (stream,elt->msgno,&j,NIL);
+ /* in case this got decremented */
+ elt->private.msg.header.offset = elt->private.special.text.size;
+ /* header size, sans trailing newline */
+ if ((j < 4) || (s[j - 4] == '\r')) j -= 2;
+ if (j != elt->private.spare.data) fatal ("header size inconsistent");
+ /* protection pointer moves to RFC822 text */
+ f.protect = elt->private.special.offset +
+ elt->private.msg.text.offset;
+ unix_write (&f,s,j); /* write RFC822 header */
+ /* write status and UID */
+ unix_write (&f,LOCAL->buf,
+ j = unix_xstatus (stream,LOCAL->buf,elt,NIL,flag));
+ flag = 1; /* only write X-IMAPbase once */
+ /* new file header size */
+ elt->private.msg.header.text.size = elt->private.spare.data + j;
+
+ /* did text move? */
+ if (f.curpos != f.protect) {
+ /* get message text */
+ s = unix_text_work (stream,elt,&j,FT_INTERNAL);
+ /* can't happen it says here */
+ if (j > elt->private.msg.text.text.size)
+ fatal ("text size inconsistent");
+ /* new text offset, status/UID may change it */
+ elt->private.msg.text.offset = f.curpos - newoffset;
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset : (f.curpos + j + 2);
+ unix_write (&f,s,j);/* write text */
+ /* write trailing newline */
+ unix_write (&f,"\r\n",2);
+ }
+ else { /* tie off header and status */
+ unix_write (&f,NIL,NIL);
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset : size;
+ /* locate end of message text */
+ j = f.filepos + elt->private.msg.text.text.size;
+ /* trailing newline already there? */
+ if (f.protect == (off_t) (j + 2)) f.curpos = f.filepos = f.protect;
+ else { /* trailing newline missing, write it */
+ f.curpos = f.filepos = j;
+ unix_write (&f,"\r\n",2);
+ }
+ }
+ /* new internal header offset */
+ elt->private.special.offset = newoffset;
+ elt->private.dirty =NIL;/* message is now clean */
+ }
+ else { /* no need to rewrite this message */
+ /* tie off previous message if needed */
+ unix_write (&f,NIL,NIL);
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset : size;
+ /* locate end of message text */
+ j = f.filepos + elt->private.special.text.size +
+ elt->private.msg.header.text.size +
+ elt->private.msg.text.text.size;
+ /* trailing newline already there? */
+ if (f.protect == (off_t) (j + 2)) f.curpos = f.filepos = f.protect;
+ else { /* trailing newline missing, write it */
+ f.curpos = f.filepos = j;
+ unix_write (&f,"\r\n",2);
+ }
+ }
+ }
+ }
+
+ unix_write (&f,NIL,NIL); /* tie off final message */
+ if (size != ((unsigned long) f.filepos)) fatal ("file size inconsistent");
+ fs_give ((void **) &f.buf); /* free buffer */
+ /* make sure tied off */
+ ftruncate (LOCAL->fd,LOCAL->filesize = size);
+ fsync (LOCAL->fd); /* make sure the updates take */
+ if (size && (flag < 0)) fatal ("lost UID base information");
+ /* no longer dirty */
+ LOCAL->ddirty = LOCAL->dirty = NIL;
+ /* notify upper level of new mailbox sizes */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ /* set atime to now, mtime a second earlier */
+ times.modtime = (times.actime = time (0)) -1;
+ /* set the times, note change */
+ if (!utime (stream->mailbox,&times)) LOCAL->filetime = times.modtime;
+ /* flush the lock file */
+ unix_unlock (LOCAL->fd,stream,lock);
+ }
+ return ret; /* return state from algorithm */
+}
+
+/* Extend UNIX mailbox file
+ * Accepts: MAIL stream
+ * new desired size
+ * Return: T if success, else NIL
+ */
+
+long unix_extend (MAILSTREAM *stream,unsigned long size)
+{
+ unsigned long i = (size > ((unsigned long) LOCAL->filesize)) ?
+ size - ((unsigned long) LOCAL->filesize) : 0;
+ if (i) { /* does the mailbox need to grow? */
+ if (i > LOCAL->buflen) { /* make sure have enough space */
+ /* this user won the lottery all right */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1);
+ }
+ memset (LOCAL->buf,'\0',i); /* get a block of nulls */
+ while (T) { /* until write successful or punt */
+ lseek (LOCAL->fd,LOCAL->filesize,L_SET);
+ if ((write (LOCAL->fd,LOCAL->buf,i) >= 0) && !fsync (LOCAL->fd)) break;
+ else {
+ long e = errno; /* note error before doing ftruncate */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ if (mm_diskerror (stream,e,NIL)) {
+ fsync (LOCAL->fd); /* user chose to punt */
+ sprintf (LOCAL->buf,"Unable to extend mailbox: %s",strerror (e));
+ if (!stream->silent) mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ }
+ }
+ }
+ return LONGT;
+}
+
+/* Write data to buffered file
+ * Accepts: buffered file pointer
+ * file data or NIL to indicate "flush buffer"
+ * date size (ignored for "flush buffer")
+ * Does not return until success
+ */
+
+void unix_write (UNIXFILE *f,char *buf,unsigned long size)
+{
+ unsigned long i,j,k;
+ if (buf) { /* doing buffered write? */
+ i = f->bufpos - f->buf; /* yes, get size of current buffer data */
+ /* yes, have space in current buffer chunk? */
+ if (j = i ? ((f->buflen - i) % OVERFLOWBUFLEN) : f->buflen) {
+ /* yes, fill up buffer as much as we can */
+ memcpy (f->bufpos,buf,k = min (j,size));
+ f->bufpos += k; /* new buffer position */
+ f->curpos += k; /* new current position */
+ if (j -= k) return; /* all done if still have buffer free space */
+ buf += k; /* full, get new unwritten data pointer */
+ size -= k; /* new data size */
+ i += k; /* new buffer data size */
+ }
+ /* This chunk of the buffer is full. See if can make some space by
+ * writing to the disk, if there's enough unprotected space to do so.
+ * Try to fill out any unaligned chunk, along with any subsequent full
+ * chunks that will fit in unprotected space.
+ */
+ /* any unprotected space we can write to? */
+ if (j = min (i,f->protect - f->filepos)) {
+ /* yes, filepos not at chunk boundary? */
+ if ((k = f->filepos % OVERFLOWBUFLEN) && ((k = OVERFLOWBUFLEN - k) < j))
+ j -= k; /* yes, and can write out partial chunk */
+ else k = 0; /* no partial chunk to write */
+ /* if at least a chunk free, write that too */
+ if (j > OVERFLOWBUFLEN) k += j - (j % OVERFLOWBUFLEN);
+ if (k) { /* write data if there is anything we can */
+ unix_phys_write (f,f->buf,k);
+ /* slide buffer */
+ if (i -= k) memmove (f->buf,f->buf + k,i);
+ f->bufpos = f->buf + i; /* new end of buffer */
+ }
+ }
+
+ /* Have flushed the buffer as best as possible. All done if no more
+ * data to write. Otherwise, if the buffer is empty AND if the unwritten
+ * data is larger than a chunk AND the unprotected space is also larger
+ * than a chunk, then write as many chunks as we can directly from the
+ * data. Buffer the rest, expanding the buffer as needed.
+ */
+ if (size) { /* have more data that we need to buffer? */
+ /* can write any of it to disk instead? */
+ if ((f->bufpos == f->buf) &&
+ ((j = min (f->protect - f->filepos,size)) > OVERFLOWBUFLEN)) {
+ /* write as much as we can right now */
+ unix_phys_write (f,buf,j -= (j % OVERFLOWBUFLEN));
+ buf += j; /* new data pointer */
+ size -= j; /* new data size */
+ f->curpos += j; /* advance current pointer */
+ }
+ if (size) { /* still have data that we need to buffer? */
+ /* yes, need to expand the buffer? */
+ if ((i = ((f->bufpos + size) - f->buf)) > f->buflen) {
+ /* note current position in buffer */
+ j = f->bufpos - f->buf;
+ i += OVERFLOWBUFLEN; /* yes, grow another chunk */
+ fs_resize ((void **) &f->buf,f->buflen = i - (i % OVERFLOWBUFLEN));
+ /* in case buffer relocated */
+ f->bufpos = f->buf + j;
+ }
+ /* buffer remaining data */
+ memcpy (f->bufpos,buf,size);
+ f->bufpos += size; /* new end of buffer */
+ f->curpos += size; /* advance current pointer */
+ }
+ }
+ }
+ else { /* flush buffer to disk */
+ unix_phys_write (f,f->buf,i = f->bufpos - f->buf);
+ f->bufpos = f->buf; /* reset buffer */
+ /* update positions */
+ f->curpos = f->protect = f->filepos;
+ }
+}
+
+/* Physical disk write
+ * Accepts: buffered file pointer
+ * buffer address
+ * buffer size
+ * Does not return until success
+ */
+
+void unix_phys_write (UNIXFILE *f,char *buf,size_t size)
+{
+ MAILSTREAM *stream = f->stream;
+ /* write data at desired position */
+ while (size && ((lseek (LOCAL->fd,f->filepos,L_SET) < 0) ||
+ (write (LOCAL->fd,buf,size) < 0))) {
+ int e;
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Unable to write to mailbox: %s",strerror (e = errno));
+ mm_log (tmp,ERROR);
+ mm_diskerror (NIL,e,T); /* serious problem, must retry */
+ }
+ f->filepos += size; /* update file position */
+}
diff --git a/imap/src/osdep/os2/unixnt.h b/imap/src/osdep/os2/unixnt.h
new file mode 100644
index 00000000..1305184f
--- /dev/null
+++ b/imap/src/osdep/os2/unixnt.h
@@ -0,0 +1,161 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 20 December 1989
+ * Last Edited: 30 August 2006
+ */
+
+
+/* DEDICATION
+ *
+ * This file is dedicated to my dog, Unix, also known as Yun-chan and
+ * Unix J. Terwilliker Jehosophat Aloysius Monstrosity Animal Beast. Unix
+ * passed away at the age of 11 1/2 on September 14, 1996, 12:18 PM PDT, after
+ * a two-month bout with cirrhosis of the liver.
+ *
+ * He was a dear friend, and I miss him terribly.
+ *
+ * Lift a leg, Yunie. Luv ya forever!!!!
+ */
+
+/* Validate line
+ * Accepts: pointer to candidate string to validate as a From header
+ * return pointer to end of date/time field
+ * return pointer to offset from t of time (hours of ``mmm dd hh:mm'')
+ * return pointer to offset from t of time zone (if non-zero)
+ * Returns: t,ti,zn set if valid From string, else ti is NIL
+ */
+
+#define VALID(s,x,ti,zn) { \
+ ti = 0; \
+ if ((*s == 'F') && (s[1] == 'r') && (s[2] == 'o') && (s[3] == 'm') && \
+ (s[4] == ' ')) { \
+ for (x = s + 5; *x && *x != '\012'; x++); \
+ if (*x) { \
+ if (x[-1] == '\015') --x; \
+ if (x - s >= 41) { \
+ for (zn = -1; x[zn] != ' '; zn--); \
+ if ((x[zn-1] == 'm') && (x[zn-2] == 'o') && (x[zn-3] == 'r') && \
+ (x[zn-4] == 'f') && (x[zn-5] == ' ') && (x[zn-6] == 'e') && \
+ (x[zn-7] == 't') && (x[zn-8] == 'o') && (x[zn-9] == 'm') && \
+ (x[zn-10] == 'e') && (x[zn-11] == 'r') && (x[zn-12] == ' '))\
+ x += zn - 12; \
+ } \
+ if (x - s >= 27) { \
+ if (x[-5] == ' ') { \
+ if (x[-8] == ':') zn = 0,ti = -5; \
+ else if (x[-9] == ' ') ti = zn = -9; \
+ else if ((x[-11] == ' ') && ((x[-10]=='+') || (x[-10]=='-'))) \
+ ti = zn = -11; \
+ } \
+ else if (x[-4] == ' ') { \
+ if (x[-9] == ' ') zn = -4,ti = -9; \
+ } \
+ else if (x[-6] == ' ') { \
+ if ((x[-11] == ' ') && ((x[-5] == '+') || (x[-5] == '-'))) \
+ zn = -6,ti = -11; \
+ } \
+ if (ti && !((x[ti - 3] == ':') && \
+ (x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') && \
+ (x[ti - 3] == ' ') && (x[ti - 7] == ' ') && \
+ (x[ti - 11] == ' '))) ti = 0; \
+ } \
+ } \
+ } \
+}
+
+/* You are not expected to understand this macro, but read the next page if
+ * you are not faint of heart.
+ *
+ * Known formats to the VALID macro are:
+ * From user Wed Dec 2 05:53 1992
+ * BSD From user Wed Dec 2 05:53:22 1992
+ * SysV From user Wed Dec 2 05:53 PST 1992
+ * rn From user Wed Dec 2 05:53:22 PST 1992
+ * From user Wed Dec 2 05:53 -0700 1992
+ * emacs From user Wed Dec 2 05:53:22 -0700 1992
+ * From user Wed Dec 2 05:53 1992 PST
+ * From user Wed Dec 2 05:53:22 1992 PST
+ * From user Wed Dec 2 05:53 1992 -0700
+ * Solaris From user Wed Dec 2 05:53:22 1992 -0700
+ *
+ * Plus all of the above with `` remote from xxx'' after it. Thank you very
+ * much, smail and Solaris, for making my life considerably more complicated.
+ */
+
+/*
+ * What? You want to understand the VALID macro anyway? Alright, since you
+ * insist. Actually, it isn't really all that difficult, provided that you
+ * take it step by step.
+ *
+ * Line 1 Initializes the return ti value to failure (0);
+ * Lines 2-3 Validates that the 1st-5th characters are ``From ''.
+ * Lines 4-6 Validates that there is an end of line and points x at it.
+ * Lines 7-14 First checks to see if the line is at least 41 characters long.
+ * If so, it scans backwards to find the rightmost space. From
+ * that point, it scans backwards to see if the string matches
+ * `` remote from''. If so, it sets x to point to the space at
+ * the start of the string.
+ * Line 15 Makes sure that there are at least 27 characters in the line.
+ * Lines 16-21 Checks if the date/time ends with the year (there is a space
+ * five characters back). If there is a colon three characters
+ * further back, there is no timezone field, so zn is set to 0
+ * and ti is set in front of the year. Otherwise, there must
+ * either to be a space four characters back for a three-letter
+ * timezone, or a space six characters back followed by a + or -
+ * for a numeric timezone; in either case, zn and ti become the
+ * offset of the space immediately before it.
+ * Lines 22-24 Are the failure case for line 14. If there is a space four
+ * characters back, it is a three-letter timezone; there must be a
+ * space for the year nine characters back. zn is the zone
+ * offset; ti is the offset of the space.
+ * Lines 25-28 Are the failure case for line 20. If there is a space six
+ * characters back, it is a numeric timezone; there must be a
+ * space eleven characters back and a + or - five characters back.
+ * zn is the zone offset; ti is the offset of the space.
+ * Line 29-32 If ti is valid, make sure that the string before ti is of the
+ * form www mmm dd hh:mm or www mmm dd hh:mm:ss, otherwise
+ * invalidate ti. There must be a colon three characters back
+ * and a space six or nine characters back (depending upon
+ * whether or not the character six characters back is a colon).
+ * There must be a space three characters further back (in front
+ * of the day), one seven characters back (in front of the month),
+ * and one eleven characters back (in front of the day of week).
+ * ti is set to be the offset of the space before the time.
+ *
+ * Why a macro? It gets invoked a *lot* in a tight loop. On some of the
+ * newer pipelined machines it is faster being open-coded than it would be if
+ * subroutines are called.
+ *
+ * Why does it scan backwards from the end of the line, instead of doing the
+ * much easier forward scan? There is no deterministic way to parse the
+ * ``user'' field, because it may contain unquoted spaces! Yes, I tested it to
+ * see if unquoted spaces were possible. They are, and I've encountered enough
+ * evil mail to be totally unwilling to trust that ``it will never happen''.
+ */
+
+/* Build parameters */
+
+#define KODRETRY 15 /* kiss-of-death retry in seconds */
+#define LOCKTIMEOUT 5 /* lock timeout in minutes */
diff --git a/imap/src/osdep/os2/write.c b/imap/src/osdep/os2/write.c
new file mode 100644
index 00000000..c7854815
--- /dev/null
+++ b/imap/src/osdep/os2/write.c
@@ -0,0 +1,59 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Write data, treating partial writes as an error
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 May 1995
+ * Last Edited: 30 August 2006
+ */
+
+/* The whole purpose of this unfortunate routine is to deal with DOS and
+ * certain cretinous versions of UNIX which decided that the "bytes actually
+ * written" return value from write() gave them license to use that for things
+ * that are really errors, such as disk quota exceeded, maximum file size
+ * exceeded, disk full, etc.
+ *
+ * BSD won't screw us this way on the local filesystem, but who knows what
+ * some NFS-mounted filesystem will do.
+ */
+
+#undef write
+
+/* Write data to file
+ * Accepts: file descriptor
+ * I/O vector structure
+ * number of vectors in structure
+ * Returns: number of bytes written if successful, -1 if failure
+ */
+
+long maxposint = (long)((((unsigned long) 1) << ((sizeof(int) * 8) - 1)) - 1);
+
+long safe_write (int fd,char *buf,long nbytes)
+{
+ long i,j;
+ if (nbytes > 0) for (i = nbytes; i; i -= j,buf += j) {
+ while (((j = write (fd,buf,(int) min (maxposint,i))) < 0) &&
+ (errno == EINTR));
+ if (j < 0) return j;
+ }
+ return nbytes;
+}
diff --git a/imap/src/osdep/tops-20/build.ctl b/imap/src/osdep/tops-20/build.ctl
new file mode 100644
index 00000000..45a13450
--- /dev/null
+++ b/imap/src/osdep/tops-20/build.ctl
@@ -0,0 +1,63 @@
+! ========================================================================
+! Copyright 1988-2006 University of Washington
+!
+! Licensed under the Apache License, Version 2.0 (the "License");
+! you may not use this file except in compliance with the License.
+! You may obtain a copy of the License at
+!
+! http://www.apache.org/licenses/LICENSE-2.0
+!
+!
+! ========================================================================
+
+! Program: Portable C client build for TOPS-20
+!
+! Author: Mark Crispin
+! Networks and Distributed Computing
+! Computing & Communications
+! University of Washington
+! Administration Building, AG-44
+! Seattle, WA 98195
+! Internet: MRC@CAC.Washington.EDU
+!
+! Date: 11 May 1989
+! Last Edited: 30 August 2006
+
+!
+! Set environment
+@DEFINE SYS: PS:<KCC-6>,SYS:
+!
+! Build c-client library
+!
+@COPY OS_T20.* OSDEP.*
+@CCX -c -m -w=note MAIL.C DUMMYT20.C IMAP4R1.C SMTP.C NNTP.C POP3.C RFC822.C MISC.C OSDEP.C FLSTRING.C SMANAGER.C NEWSRC.C NETMSG.C UTF8.C UTF8AUX.C
+!
+! Build c-client library file. Should use MAKLIB, but MAKLIB barfs on MAIL.REL
+!
+@DELETE CCLINT.REL
+@APPEND MAIL.REL,DUMMYT20.REL,IMAP4R1.REL,SMTP.REL,NNTP.REL,POP3.REL,RFC822.REL,MISC.REL,OSDEP.REL,FLSTRING.REL,SMANAGER.REL,NEWSRC.REL,NETMSG.REL,UTF8.REL,UTF8AUX.REL CCLINT.REL
+!
+! Build MTEST library test program
+!
+@CCX -c -m -w=note MTEST.C
+@LINK
+*/SET:.HIGH.:200000
+*C:LIBCKX.REL
+*MTEST.REL
+*CCLINT.REL
+*MTEST/SAVE
+*/GO
+!
+! Build MAILUTIL
+!
+@CCX -c -m -w=note MAILUTIL.C
+@RENAME MAILUTIL.REL MUTIL.REL
+@LINK
+*/SET:.HIGH.:200000
+*C:LIBCKX.REL
+*MUTIL.REL
+*CCLINT.REL
+*MUTIL/SAVE
+*/GO
+@RESET
+@RENAME MUTIL.EXE MAILUTIL.EXE
diff --git a/imap/src/osdep/tops-20/dummy.h b/imap/src/osdep/tops-20/dummy.h
new file mode 100644
index 00000000..32650e06
--- /dev/null
+++ b/imap/src/osdep/tops-20/dummy.h
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 9 May 1991
+ * Last Edited: 30 August 2006
+ */
+
+/* Exported function prototypes */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat);
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long scan_contents (DRIVER *dtb,char *name,char *contents,
+ unsigned long csiz,unsigned long fsiz);
+long dummy_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz);
+long dummy_create (MAILSTREAM *stream,char *mailbox);
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode);
+long dummy_delete (MAILSTREAM *stream,char *mailbox);
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname);
+char *dummy_file (char *dst,char *name);
+long dummy_canonicalize (char *tmp,char *ref,char *pat);
diff --git a/imap/src/osdep/tops-20/dummyt20.c b/imap/src/osdep/tops-20/dummyt20.c
new file mode 100644
index 00000000..1117ad45
--- /dev/null
+++ b/imap/src/osdep/tops-20/dummyt20.c
@@ -0,0 +1,294 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines for TOPS-20
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 13 June 1995
+ * Last Edited: 30 August 2006
+ */
+
+
+#include <ctype.h>
+#include <stdio.h>
+#include "mail.h"
+#include "osdep.h"
+#include "dummy.h"
+#include "misc.h"
+
+/* Function prototypes */
+
+DRIVER *dummy_valid (char *name);
+void *dummy_parameters (long function,void *value);
+MAILSTREAM *dummy_open (MAILSTREAM *stream);
+void dummy_close (MAILSTREAM *stream,long options);
+long dummy_ping (MAILSTREAM *stream);
+void dummy_check (MAILSTREAM *stream);
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options);
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+/* Dummy routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER dummydriver = {
+ "dummy", /* driver name */
+ DR_LOCAL|DR_MAIL, /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ dummy_valid, /* mailbox is valid for us */
+ dummy_parameters, /* manipulate parameters */
+ dummy_scan, /* scan mailboxes */
+ dummy_list, /* list mailboxes */
+ dummy_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ dummy_delete, /* delete mailbox */
+ dummy_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ dummy_open, /* open mailbox */
+ dummy_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message structure */
+ NIL, /* fetch header */
+ NIL, /* fetch text */
+ NIL, /* fetch message data */
+ NIL, /* unique identifier */
+ NIL, /* message number from UID */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ dummy_ping, /* ping mailbox to see if still alive */
+ dummy_check, /* check for new messages */
+ dummy_expunge, /* expunge deleted messages */
+ dummy_copy, /* copy messages to another mailbox */
+ dummy_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+
+ /* prototype stream */
+MAILSTREAM dummyproto = {&dummydriver};
+
+/* Dummy validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *dummy_valid (char *name)
+{
+ /* must be valid local mailbox */
+ return (name && *name && (*name != '{') && !compare_cstring (name,"INBOX")) ?
+ &dummydriver : NIL;
+}
+
+
+/* Dummy manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *dummy_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* Dummy scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ /* return silently */
+}
+
+
+/* Dummy list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ /* return silently */
+}
+
+
+/* Dummy list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ /* return silently */
+}
+
+/* Dummy create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * driver type to use
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create (MAILSTREAM *stream,char *mailbox)
+{
+ return NIL; /* always fails */
+}
+
+
+/* Dummy delete mailbox
+ * Accepts: mail stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return NIL; /* always fails */
+}
+
+
+/* Mail rename mailbox
+ * Accepts: mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ return NIL; /* always fails */
+}
+
+/* Dummy open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *dummy_open (MAILSTREAM *stream)
+{
+ char tmp[MAILTMPLEN];
+ /* OP_PROTOTYPE call or silence */
+ if (!stream || stream->silent) return NIL;
+ if (compare_cstring (stream->mailbox,"INBOX")) {
+ sprintf (tmp,"Not a mailbox: %s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ return NIL; /* always fails */
+ }
+ if (!stream->silent) { /* only if silence not requested */
+ mail_exists (stream,0); /* say there are 0 messages */
+ mail_recent (stream,0);
+ stream->uid_validity = time (0);
+ }
+ stream->inbox = T; /* note that it's an INBOX */
+ return stream; /* return success */
+}
+
+
+/* Dummy close
+ * Accepts: MAIL stream
+ * options
+ */
+
+void dummy_close (MAILSTREAM *stream,long options)
+{
+ /* return silently */
+}
+
+/* Dummy ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+long dummy_ping (MAILSTREAM *stream)
+{
+ return T;
+}
+
+
+/* Dummy check mailbox
+ * Accepts: MAIL stream
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+void dummy_check (MAILSTREAM *stream)
+{
+ dummy_ping (stream); /* invoke ping */
+}
+
+
+/* Dummy expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ return LONGT;
+}
+
+/* Dummy copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * options
+ * Returns: T if copy successful, else NIL
+ */
+
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) fatal ("Impossible dummy_copy");
+ return NIL;
+}
+
+
+/* Dummy append message string
+ * Accepts: mail stream
+ * destination mailbox
+ * append callback function
+ * data for callback
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Can't append to %s",mailbox);
+ mm_log (tmp,ERROR); /* pass up error */
+ return NIL; /* always fails */
+}
diff --git a/imap/src/osdep/tops-20/env_t20.c b/imap/src/osdep/tops-20/env_t20.c
new file mode 100644
index 00000000..38403454
--- /dev/null
+++ b/imap/src/osdep/tops-20/env_t20.c
@@ -0,0 +1,226 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Environment routines -- TOPS-20 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Dedication:
+ * This file is dedicated with affection to the TOPS-20 operating system, which
+ * set standards for user and programmer friendliness that have still not been
+ * equaled by more `modern' operating systems.
+ * Wasureru mon ka!!!!
+ */
+
+/* c-client environment parameters */
+
+static char *myUserName = NIL; /* user name */
+static char *myHomeDir = NIL; /* home directory name */
+static char *myLocalHost = NIL; /* local host name */
+static char *myNewsrc = NIL; /* newsrc file name */
+static short no822tztext = NIL; /* disable RFC [2]822 timezone text */
+
+
+#include "pmatch.c" /* include wildcard pattern matcher */
+
+/* Environment manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *env_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_USERNAME:
+ if (myUserName) fs_give ((void **) &myUserName);
+ myUserName = cpystr ((char *) value);
+ case GET_USERNAME:
+ ret = (void *) myUserName;
+ break;
+ case SET_HOMEDIR:
+ if (myHomeDir) fs_give ((void **) &myHomeDir);
+ myHomeDir = cpystr ((char *) value);
+ case GET_HOMEDIR:
+ ret = (void *) myHomeDir;
+ break;
+ case SET_LOCALHOST:
+ if (myLocalHost) fs_give ((void **) &myLocalHost);
+ myLocalHost = cpystr ((char *) value);
+ case GET_LOCALHOST:
+ ret = (void *) myLocalHost;
+ break;
+ case SET_NEWSRC:
+ if (myNewsrc) fs_give ((void **) &myNewsrc);
+ myNewsrc = cpystr ((char *) value);
+ case GET_NEWSRC:
+ ret = (void *) myNewsrc;
+ break;
+ case SET_DISABLE822TZTEXT:
+ no822tztext = value ? T : NIL;
+ case GET_DISABLE822TZTEXT:
+ ret = (void *) (no822tztext ? VOIDT : NIL);
+ break;
+ }
+ return ret;
+}
+
+/* Write current time in RFC 822 format
+ * Accepts: destination string
+ */
+
+void rfc822_date (char *date)
+{
+ char *s;
+ int argblk[4];
+ argblk[1] = (int) (date-1);
+ argblk[2] = -1; /* time now */
+ argblk[3] = OT_822; /* want RFC [2]822 format */
+ jsys (ODTIM,argblk);
+ /* suppress time zone text if desired */
+ if (no822tztext && (s = strstr (date," ("))) *s = NIL;
+}
+
+
+/* Write current time in internal format
+ * Accepts: destination string
+ */
+
+void internal_date (char *date)
+{
+ int argblk[5];
+ argblk[1] = (int) (date-1);
+ argblk[2] = -1; /* time now */
+ argblk[3] = OT_4YR; /* output in 4-digit year format */
+ jsys (ODTIM,argblk);
+ argblk[2] = ' '; /* delimit with space */
+ jsys (BOUT,argblk);
+ argblk[2] = -1; /* time now */
+ argblk[4] = 0; /* no flags */
+ jsys (ODCNV,argblk); /* get time zone */
+ argblk[2] = ((argblk[4] & 077000000) >> 18) * -100;
+ /* add an hour if summer time */
+ if (argblk[4] & IC_ADS) argblk[2] += 100;
+ argblk[3] = 0340005000012;
+ jsys (NOUT,argblk);
+}
+
+/* Return my user name
+ * Accepts: pointer to optional flags
+ * Returns: my user name
+ */
+
+char *myusername_full (unsigned long *flags)
+{
+ if (!myUserName) { /* get user name if don't have it yet */
+ char tmp[MAILTMPLEN];
+ int argblk[5],i;
+ jsys (GJINF,argblk); /* get job poop */
+ if (!(i = argblk[1])) { /* remember user number */
+ if (flags) *flags = MU_NOTLOGGEDIN;
+ return "SYSTEM"; /* not logged in */
+ }
+ argblk[1] = (int) (tmp-1); /* destination */
+ argblk[2] = i; /* user number */
+ jsys (DIRST,argblk); /* get user name string */
+ myUserName = cpystr (tmp); /* copy user name */
+ argblk[1] = 0; /* no flags */
+ argblk[2] = i; /* user number */
+ argblk[3] = 0; /* no stepping */
+ jsys (RCDIR,argblk); /* get home directory */
+ argblk[1] = (int) (tmp-1); /* destination */
+ argblk[2] = argblk[3]; /* home directory number */
+ jsys (DIRST,argblk); /* get home directory string */
+ myHomeDir = cpystr (tmp); /* copy home directory */
+ if (!myNewsrc) { /* set news file name if not defined */
+ sprintf (tmp,"%sNEWSRC",myhomedir ());
+ myNewsrc = cpystr (tmp);
+ }
+ if (flags) *flags = MU_LOGGEDIN;
+ }
+ return myUserName;
+}
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost ()
+{
+ if (!myLocalHost) { /* initialize if first time */
+ char tmp[MAILTMPLEN];
+ int argblk[5];
+ argblk[1] = _GTHNS; /* convert number to string */
+ argblk[2] = (int) (tmp-1);
+ argblk[3] = -1; /* want local host */
+ if (!jsys (GTHST,argblk)) strcpy (tmp,"LOCAL");
+ myLocalHost = cpystr (tmp);
+ }
+ return myLocalHost;
+}
+
+
+/* Return my home directory name
+ * Returns: my home directory name
+ */
+
+char *myhomedir ()
+{
+ if (!myHomeDir) myusername ();/* initialize if first time */
+ return myHomeDir ? myHomeDir : "";
+}
+
+
+/* Determine default prototype stream to user
+ * Accepts: type (NIL for create, T for append)
+ * Returns: default prototype stream
+ */
+
+MAILSTREAM *default_proto (long type)
+{
+ return NIL; /* no default prototype */
+}
+
+/* Emulator for BSD syslog() routine
+ * Accepts: priority
+ * message
+ * parameters
+ */
+
+void syslog (int priority,const char *message,...)
+{
+}
+
+
+/* Emulator for BSD openlog() routine
+ * Accepts: identity
+ * options
+ * facility
+ */
+
+void openlog (const char *ident,int logopt,int facility)
+{
+}
diff --git a/imap/src/osdep/tops-20/env_t20.h b/imap/src/osdep/tops-20/env_t20.h
new file mode 100644
index 00000000..3031f4ed
--- /dev/null
+++ b/imap/src/osdep/tops-20/env_t20.h
@@ -0,0 +1,73 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: TOPS-20 environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 25 May 1995
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Dedication:
+ * This file is dedicated with affection to the TOPS-20 operating system, which
+ * set standards for user and programmer friendliness that have still not been
+ * equaled by more `modern' operating systems.
+ * Wasureru mon ka!!!!
+ */
+
+
+#define SUBSCRIPTIONFILE(t) sprintf (t,"%s\\SUBSCRIPTIONS.TXT",myhomedir ())
+#define SUBSCRIPTIONTEMP(t) sprintf (t,"%s\\SUBSCRIPTIONS.TMP",myhomedir ())
+
+/* Function prototypes */
+
+#include "env.h"
+
+char *myusername_full (unsigned long *flags);
+#define MU_LOGGEDIN 0
+#define MU_NOTLOGGEDIN 1
+#define MU_ANONYMOUS 2
+#define myusername() \
+ myusername_full (NIL)
+
+
+/* syslog() emulation */
+
+#define LOG_MAIL (2<<3) /* mail system */
+#define LOG_DAEMON (3<<3) /* system daemons */
+#define LOG_AUTH (4<<3) /* security/authorization messages */
+#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 signification condition */
+#define LOG_INFO 6 /* informational */
+#define LOG_DEBUG 7 /* debug-level messages */
+#define LOG_PID 0x01 /* log the pid with each message */
+#define LOG_CONS 0x02 /* log on the console if errors in sending */
+#define LOG_ODELAY 0x04 /* delay open until syslog() is called */
+#define LOG_NDELAY 0x08 /* don't delay open */
+#define LOG_NOWAIT 0x10 /* if forking to log on console, don't wait() */
+
+void openlog (const char *ident,int logopt,int facility);
+void syslog (int priority,const char *message,...);
diff --git a/imap/src/osdep/tops-20/fs_t20.c b/imap/src/osdep/tops-20/fs_t20.c
new file mode 100644
index 00000000..03908328
--- /dev/null
+++ b/imap/src/osdep/tops-20/fs_t20.c
@@ -0,0 +1,62 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Free storage management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Get a block of free storage
+ * Accepts: size of desired block
+ * Returns: free storage block
+ */
+
+void *fs_get (size_t size)
+{
+ void *block = malloc (size ? size : (size_t) 1);
+ if (!block) fatal ("Out of memory");
+ return (block);
+}
+
+
+/* Resize a block of free storage
+ * Accepts: ** pointer to current block
+ * new size
+ */
+
+void fs_resize (void **block,size_t size)
+{
+ if (!(*block = realloc (*block,size ? size : (size_t) 1)))
+ fatal ("Can't resize memory");
+}
+
+
+/* Return a block of free storage
+ * Accepts: ** pointer to free storage block
+ */
+
+void fs_give (void **block)
+{
+ free (*block);
+ *block = NIL;
+}
diff --git a/imap/src/osdep/tops-20/ftl_t20.c b/imap/src/osdep/tops-20/ftl_t20.c
new file mode 100644
index 00000000..9e65ef55
--- /dev/null
+++ b/imap/src/osdep/tops-20/ftl_t20.c
@@ -0,0 +1,38 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: DOS/VMS/TOPS-20 crash management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Report a fatal error
+ * Accepts: string to output
+ */
+
+void fatal (char *string)
+{
+ mm_fatal (string); /* pass up the string */
+ abort (); /* die horribly */
+}
diff --git a/imap/src/osdep/tops-20/linkage.c b/imap/src/osdep/tops-20/linkage.c
new file mode 100644
index 00000000..14490a0c
--- /dev/null
+++ b/imap/src/osdep/tops-20/linkage.c
@@ -0,0 +1,37 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Default driver linkage
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 13 June 1995
+ * Last Edited: 23 May 2007
+ */
+
+ mail_link (&imapdriver); /* link in the imap driver */
+ mail_link (&nntpdriver); /* link in the nntp driver */
+ mail_link (&pop3driver); /* link in the pop3 driver */
+ mail_link (&dummydriver); /* link in the dummy driver */
+ auth_link (&auth_ext); /* link in the ext authenticator */
+ auth_link (&auth_md5); /* link in the md5 authenticator */
+ auth_link (&auth_pla); /* link in the plain authenticator */
+ auth_link (&auth_log); /* link in the log authenticator */
+ mail_versioncheck (CCLIENTVERSION); /* validate version */
diff --git a/imap/src/osdep/tops-20/linkage.h b/imap/src/osdep/tops-20/linkage.h
new file mode 100644
index 00000000..7e0eccfa
--- /dev/null
+++ b/imap/src/osdep/tops-20/linkage.h
@@ -0,0 +1,36 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Default driver linkage
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 13 June 1995
+ * Last Edited: 30 August 2006
+ */
+
+extern DRIVER imapdriver;
+extern DRIVER nntpdriver;
+extern DRIVER pop3driver;
+extern DRIVER dummydriver;
+extern AUTHENTICATOR auth_ext;
+extern AUTHENTICATOR auth_log;
+extern AUTHENTICATOR auth_md5;
+extern AUTHENTICATOR auth_pla;
diff --git a/imap/src/osdep/tops-20/log_t20.c b/imap/src/osdep/tops-20/log_t20.c
new file mode 100644
index 00000000..1c744d7f
--- /dev/null
+++ b/imap/src/osdep/tops-20/log_t20.c
@@ -0,0 +1,80 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: TOPS-20 server login
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Dedication:
+ * This file is dedicated with affection to the TOPS-20 operating system, which
+ * set standards for user and programmer friendliness that have still not been
+ * equaled by more `modern' operating systems.
+ * Wasureru mon ka!!!!
+ */
+
+/* Server log in
+ * Accepts: user name string
+ * password string
+ * authenticating user name string
+ * argument count
+ * argument vector
+ * Returns: T if password validated, NIL otherwise
+ */
+
+long server_login (char *user,char *pass,char *authuser,int argc,char *argv[])
+{
+ int uid;
+ int argblk[5];
+ if (authuser && *authuser) { /* not available */
+ syslog (LOG_NOTICE|LOG_AUTH,
+ "Login %s failed: invalid authentication ID %s host=%.80s",
+ user,authuser,tcp_clienthost ());
+ sleep (3);
+ return NIL;
+ }
+ argblk[1] = RC_EMO; /* require exact match */
+ argblk[2] = (int) (user-1); /* user name */
+ argblk[3] = 0; /* no stepping */
+ if (!jsys (RCUSR,argblk)) return NIL;
+ uid = argblk[1] = argblk[3]; /* user number */
+ argblk[2] = (int) (pass-1); /* password */
+ argblk[3] = 0; /* no special account */
+ if (!jsys (LOGIN,argblk)) return NIL;
+ return T;
+}
+
+
+/* Authenticated server log in
+ * Accepts: user name string
+ * authenticating user name string
+ * argument count
+ * argument vector
+ * Returns: T if password validated, NIL otherwise
+ */
+
+long authserver_login (char *user,char *authuser,int argc,char *argv[])
+{
+ return NIL; /* how to implement this on TOPS-20??? */
+}
diff --git a/imap/src/osdep/tops-20/nl_t20.c b/imap/src/osdep/tops-20/nl_t20.c
new file mode 100644
index 00000000..47cb7f0a
--- /dev/null
+++ b/imap/src/osdep/tops-20/nl_t20.c
@@ -0,0 +1,61 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Windows/TOPS-20 newline routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Copy string with CRLF newlines
+ * Accepts: destination string
+ * pointer to size of destination string buffer
+ * source string
+ * length of source string
+ * Returns: length of copied string
+ */
+
+unsigned long strcrlfcpy (unsigned char **dst,unsigned long *dstl,
+ unsigned char *src,unsigned long srcl)
+{
+ /* flush destination buffer if too small */
+ if (*dst && (srcl > *dstl)) fs_give ((void **) dst);
+ if (!*dst) { /* make a new buffer if needed */
+ *dst = (char *) fs_get ((size_t) (*dstl = srcl) + 1);
+ if (dstl) *dstl = srcl; /* return new buffer length to main program */
+ }
+ /* copy strings */
+ if (srcl) memcpy (*dst,src,(size_t) srcl);
+ *(*dst + srcl) = '\0'; /* tie off destination */
+ return srcl; /* return length */
+}
+
+
+/* Length of string after strcrlfcpy applied
+ * Accepts: source string
+ * Returns: length of string
+ */
+
+unsigned long strcrlflen (STRING *s)
+{
+ return SIZE (s); /* no-brainer on DOS! */
+}
diff --git a/imap/src/osdep/tops-20/os_t20.c b/imap/src/osdep/tops-20/os_t20.c
new file mode 100644
index 00000000..de0479df
--- /dev/null
+++ b/imap/src/osdep/tops-20/os_t20.c
@@ -0,0 +1,106 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- TOPS-20 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Dedication:
+ * This file is dedicated with affection to the TOPS-20 operating system, which
+ * set standards for user and programmer friendliness that have still not been
+ * equaled by more `modern' operating systems.
+ * Wasureru mon ka!!!!
+ */
+
+#include "mail.h"
+#include <jsys.h> /* must be before tcp_t20.h */
+#include "tcp_t20.h" /* must be before osdep include tcp.h */
+#include <time.h>
+#include "osdep.h"
+#include <sys/time.h>
+#include "misc.h"
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <ctype.h>
+
+#include "fs_t20.c"
+#include "ftl_t20.c"
+#include "nl_t20.c"
+#include "env_t20.c"
+#include "tcp_t20.c"
+#include "log_t20.c"
+
+#define MD5ENABLE "PS:<SYSTEM>CRAM-MD5.PWD"
+#include "auth_md5.c"
+#include "auth_ext.c"
+#include "auth_pla.c"
+#include "auth_log.c"
+
+/* Emulator for UNIX gethostid() call
+ * Returns: host id
+ */
+
+long gethostid ()
+{
+ int argblk[5];
+#ifndef _APRID
+#define _APRID 28
+#endif
+ argblk[1] = _APRID;
+ jsys (GETAB,argblk);
+ return (long) argblk[1];
+}
+
+
+/* Emulator for UNIX getpass() call
+ * Accepts: prompt
+ * Returns: password
+ */
+
+#define PWDLEN 128 /* used by Linux */
+
+char *getpass (const char *prompt)
+{
+ char *s;
+ static char pwd[PWDLEN];
+ int argblk[5],mode;
+ argblk[1] = (int) (prompt-1); /* prompt user */
+ jsys (PSOUT,argblk);
+ argblk[1] = _PRIIN; /* get current TTY mode */
+ jsys (RFMOD,argblk);
+ mode = argblk[2]; /* save for later */
+ argblk[2] &= ~06000;
+ jsys (SFMOD,argblk);
+ jsys (STPAR,argblk);
+ fgets (pwd,PWDLEN-1,stdin);
+ pwd[PWDLEN-1] = '\0';
+ if (s = strchr (pwd,'\n')) *s = '\0';
+ putchar ('\n');
+ argblk[2] = mode;
+ jsys (SFMOD,argblk);
+ jsys (STPAR,argblk);
+ return pwd;
+}
diff --git a/imap/src/osdep/tops-20/os_t20.h b/imap/src/osdep/tops-20/os_t20.h
new file mode 100644
index 00000000..4c397243
--- /dev/null
+++ b/imap/src/osdep/tops-20/os_t20.h
@@ -0,0 +1,52 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- TOPS-20 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 January 2007
+ */
+
+
+/* Dedication:
+ * This file is dedicated with affection to the TOPS-20 operating system, which
+ * set standards for user and programmer friendliness that have still not been
+ * equaled by more `modern' operating systems.
+ * Wasureru mon ka!!!!
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/file.h>
+#include <unistd.h>
+
+#include "env_t20.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+
+long gethostid (void);
+char *getpass (const char *prompt);
+
+#define strtok_r(a,b,c) strtok(a,b)
diff --git a/imap/src/osdep/tops-20/pmatch.c b/imap/src/osdep/tops-20/pmatch.c
new file mode 100644
index 00000000..95a0bb86
--- /dev/null
+++ b/imap/src/osdep/tops-20/pmatch.c
@@ -0,0 +1,89 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: IMAP Wildcard Matching Routines (case-independent)
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 June 2000
+ * Last Edited: 30 August 2006
+ */
+
+/* Wildcard pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if pattern matches base, else NIL
+ */
+
+long pmatch_full (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ /* % at end, OK if no inferiors */
+ if (!pat[1]) return (delim && strchr (s,delim)) ? NIL : T;
+ /* scan remainder of string until delimiter */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while ((*s != delim) && *s++);
+ break;
+ case '*': /* match 0 or more characters */
+ if (!pat[1]) return T; /* * at end, unconditional match */
+ /* scan remainder of string */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while (*s++);
+ break;
+ case '\0': /* end of pattern */
+ return *s ? NIL : T; /* success if also end of base */
+ default: /* match this character */
+ return compare_uchar (*pat,*s) ? NIL : pmatch_full (s+1,pat+1,delim);
+ }
+ return NIL;
+}
+
+/* Directory pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if base is a matching directory of pattern, else NIL
+ */
+
+long dmatch (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ if (!*s) return T; /* end of base means have a subset match */
+ if (!*++pat) return NIL; /* % at end, no inferiors permitted */
+ /* scan remainder of string until delimiter */
+ do if (dmatch (s,pat,delim)) return T;
+ while ((*s != delim) && *s++);
+ if (*s && !s[1]) return T; /* ends with delimiter, must be subset */
+ return dmatch (s,pat,delim);/* do new scan */
+ case '*': /* match 0 or more characters */
+ return T; /* unconditional match */
+ case '\0': /* end of pattern */
+ break;
+ default: /* match this character */
+ if (*s) return compare_uchar (*pat,*s) ? NIL : dmatch (s+1,pat+1,delim);
+ /* end of base, return if at delimiter */
+ else if (*pat == delim) return T;
+ break;
+ }
+ return NIL;
+}
diff --git a/imap/src/osdep/tops-20/shortsym.h b/imap/src/osdep/tops-20/shortsym.h
new file mode 100644
index 00000000..2bd54649
--- /dev/null
+++ b/imap/src/osdep/tops-20/shortsym.h
@@ -0,0 +1,608 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Definitions for compilers with 6-letter symbol limits
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 24 May 1995
+ * Last Edited: 1 January 2008
+ */
+
+#define auth_link a_link
+#define auth_log a_log
+#define auth_login_client al_cli
+#define auth_login_server al_ser
+#define auth_ext a_ext
+#define auth_external_client ae_cli
+#define auth_external_server ae_ser
+#define auth_md5 a_md5
+#define auth_md5_valid a5_val
+#define auth_md5_client a5_cli
+#define auth_md5_server a5_ser
+#define auth_pla a_pla
+#define auth_plain_client ap_cli
+#define auth_plain_server ap_ser
+#define authenticate a_auth
+#define authserver_login a_serv
+#define body_encodings bencds
+#define body_types btypes
+#define compare_csizedtext cm_szt
+#define compare_cstring cm_str
+#define compare_uchar cm_uch
+#define compare_ulong cm_uln
+#define default_proto d_prot
+#define dummy_append d_appn
+#define dummy_canonicalize d_cano
+#define dummy_check d_chck
+#define dummy_close d_clos
+#define dummy_copy d_copy
+#define dummy_create d_crea
+#define dummy_create_path d_crep
+#define dummy_delete d_del
+#define dummy_expunge d_exp
+#define dummy_file d_fil
+#define dummy_list d_list
+#define dummy_list_work d_lstw
+#define dummy_listed d_lstd
+#define dummy_lsub d_lsub
+#define dummy_open d_open
+#define dummy_parameters d_parm
+#define dummy_ping d_ping
+#define dummy_rename d_ren
+#define dummy_scan d_scan
+#define dummy_search d_srch
+#define dummy_subscribe d_subs
+#define dummy_valid d_val
+#define env_parameters e_parm
+#define fatal fatal
+#define file_string fl_str
+#define file_string_init fl_ini
+#define file_string_next fl_nxt
+#define file_string_setpos fl_sps
+#define fs_get f_get
+#define fs_give f_give
+#define fs_resize f_rsiz
+#define hash_create h_crea
+#define hash_destory h_dest
+#define hash_index h_indx
+#define hash_lookup h_lkup
+#define hash_add h_add
+#define hash_lookup_and_add h_lad
+#define imap_OK i_OK
+#define imap_acl_work i_aclw
+#define imap_append i_appn
+#define imap_append_single i_apps
+#define imap_anon i_anon
+#define imap_auth i_auth
+#define imap_cache i_cach
+#define imap_cap i_cap
+#define imap_capability i_capa
+#define imap_challenge i_chln
+#define imap_check i_chck
+#define imap_close i_clos
+#define imap_copy i_copy
+#define imap_create i_crea
+#define imap_delete i_del
+#define imap_deleteacl i_dacl
+#define imap_expunge i_expn
+#define imap_fake i_fake
+#define imap_fast i_fast
+#define imap_fetch i_fetc
+#define imap_flag i_flag
+#define imap_flags i_flgs
+#define imap_gc i_gc
+#define imap_gc_body ig_bdy
+#define imap_getacl i_gacl
+#define imap_getquota i_gqot
+#define imap_getquotaroot i_gqtr
+#define imap_host i_host
+#define imap_list i_list
+#define imap_listrights i_lrgh
+#define imap_list_work il_wrk
+#define imap_login i_logn
+#define imap_lsub i_lsub
+#define imap_manage i_man
+#define imap_msgdata i_msgd
+#define imap_msgno i_msgn
+#define imap_myrights i_mrgh
+#define imap_open i_open
+#define imap_parameters i_parm
+#define imap_parse_address ip_adr
+#define imap_parse_adrlist ip_adl
+#define imap_parse_astring ip_ast
+#define imap_parse_body ip_bdy
+#define imap_parse_body_parameter ipb_pa
+#define imap_parse_body_structure ipb_st
+#define imap_parse_capabilities ip_cap
+#define imap_parse_disposition ip_dsp
+#define imap_parse_envelope ip_env
+#define imap_parse_extension ip_ext
+#define imap_parse_flags ip_flg
+#define imap_parse_header ip_hdr
+#define imap_parse_language ip_lng
+#define imap_parse_namespace ip_nam
+#define imap_parse_reply ip_rep
+#define imap_parse_response ip_rsp
+#define imap_parse_string ip_str
+#define imap_parse_stringlist ip_stl
+#define imap_parse_thread ip_thr
+#define imap_parse_unsolicited ip_uns
+#define imap_parse_user_flag ipu_fl
+#define imap_ping i_ping
+#define imap_reform_sequence i_rfrs
+#define imap_rename i_ren
+#define imap_reply i_rep
+#define imap_response i_rspn
+#define imap_scan i_scan
+#define imap_search i_srch
+#define imap_send i_send
+#define imap_send_astring is_ast
+#define imap_send_literal is_lit
+#define imap_send_sdate iss_da
+#define imap_send_slist iss_sl
+#define imap_send_spgm iss_pg
+#define imap_send_spgm_trim iss_pt
+#define imap_send_sset iss_st
+#define imap_send_sset_work iss_sw
+#define imap_setacl i_sacl
+#define imap_setquota i_sqot
+#define imap_sort i_sort
+#define imap_sout i_sout
+#define imap_soutr i_sotr
+#define imap_status i_stat
+#define imap_structure i_stru
+#define imap_subscribe i_sub
+#define imap_thread i_thrd
+#define imap_thread_work i_thrw
+#define imap_uid i_uid
+#define imap_unsubscribe i_uns
+#define imap_valid i_val
+#define internal_date in_dat
+#define mail_append_full m_appn
+#define mail_append_multiple m_appm
+#define mail_append_set m_apps
+#define mail_auth m_auth
+#define mail_body m_body
+#define mail_cdate m_cdat
+#define mail_check m_chck
+#define mail_close_full m_clos
+#define mail_copy_full m_copy
+#define mail_create m_crea
+#define mail_criteria m_crit
+#define mail_criteria_date mc_dat
+#define mail_criteria_string mc_str
+#define mail_date m_date
+#define mail_debug m_dbug
+#define mail_delete m_del
+#define mail_dlog m_dlog
+#define mail_elt m_elt
+#define mail_exists m_exst
+#define mail_expunge_full m_expn
+#define mail_expunged m_expd
+#define mail_fetch_body fs_bdy
+#define mail_fetch_fast mf_fst
+#define mail_fetch_flags mf_flg
+#define mail_fetch_header mf_hdr
+#define mail_fetch_message mf_msg
+#define mail_fetch_mime mf_mim
+#define mail_fetch_overview mf_ovr
+#define mail_fetch_overview_sequence mf_ovs
+#define mail_fetch_overview_default mf_ovd
+#define mail_fetch_structure mf_str
+#define mail_fetch_text mf_txt
+#define mail_fetch_text_return mf_txr
+#define mail_fetch_string_return mf_tsr
+#define mail_fetchfrom mf_frm
+#define mail_fetchsubject mf_sub
+#define mail_filter m_filt
+#define mail_flag m_flag
+#define mail_free_acl mr_acl
+#define mail_free_address mr_add
+#define mail_free_body mr_bdy
+#define mail_free_body_data mrb_da
+#define mail_free_body_parameter mrb_pr
+#define mail_free_body_part mrb_pt
+#define mail_free_cache mr_cac
+#define mail_free_elt mr_elt
+#define mail_free_envelope mr_env
+#define mail_free_handle mr_han
+#define mail_free_namespace mr_nsp
+#define mail_free_quotalist mr_qtl
+#define mail_free_searchheader mrs_hd
+#define mail_free_searchor mrs_or
+#define mail_free_searchpgm mrs_pg
+#define mail_free_searchpgmlist mrs_pl
+#define mail_free_searchset mrs_st
+#define mail_free_sortpgm mr_spg
+#define mail_free_stringlist mr_sls
+#define mail_free_threadnode mr_thn
+#define mail_gc m_gc
+#define mail_gc_msg m_gcm
+#define mail_gc_body m_gcb
+#define mail_initbody m_ibdy
+#define mail_link m_link
+#define mail_list m_list
+#define mail_lock m_lock
+#define mail_longdate ml_lda
+#define mail_lookup_auth m_laut
+#define mail_lookup_auth_name m_latn
+#define mail_lsub m_lsub
+#define mail_makehandle m_mhdl
+#define mail_match_lines m_mlns
+#define mail_msgno m_msgn
+#define mail_newacl mn_acl
+#define mail_newaddr mn_add
+#define mail_newbody mn_bdy
+#define mail_newbody_parameter mnb_pr
+#define mail_newbody_part mnb_pt
+#define mail_newbody_message_part mnb_mp
+#define mail_new_cache_elt mn_elt
+#define mail_newenvelope mn_env
+#define mail_newmsg mn_msg
+#define mail_newquotalist mn_qtl
+#define mail_newsearchheader mns_hd
+#define mail_newsearchor mns_or
+#define mail_newsearchpgm mns_pg
+#define mail_newsearchpgmlist mns_pl
+#define mail_newsearchset mns_st
+#define mail_newsortpgm mn_spg
+#define mail_newstringlist mn_sls
+#define mail_newthreadnode mn_thr
+#define mail_nodebug m_ndbg
+#define mail_open m_open
+#define mail_parameters m_parm
+#define mail_parse_date mp_dat
+#define mail_parse_flags mp_flg
+#define mail_parse_set mp_set
+#define mail_partial_body mpt_bd
+#define mail_partial_text mpt_tx
+#define mail_ping m_ping
+#define mail_read m_read
+#define mail_recent m_rcent
+#define mail_rename m_ren
+#define mail_scan m_scan
+#define mail_search_addr ms_adr
+#define mail_search_body ms_bdy
+#define mail_search_default ms_def
+#define mail_search_full m_srch
+#define mail_search_gets ms_gts
+#define mail_search_header ms_hdr
+#define mail_search_header_text ms_hdt
+#define mail_search_keyword ms_key
+#define mail_search_msg ms_msg
+#define mail_search_string ms_str
+#define mail_search_string_work ms_stw
+#define mail_search_text ms_txt
+#define mail_sequence m_seq
+#define mail_shortdate m_shtd
+#define mail_skip_fwd msk_fw
+#define mail_skip_re msk_re
+#define mail_sort ml_srt
+#define mail_sort_cache ms_csh
+#define mail_sort_compare ms_cmp
+#define mail_sort_loadcache ms_lcs
+#define mail_sort_msgs ms_mgs
+#define mail_status m_stat
+#define mail_status_default m_stad
+#define mail_stream m_strm
+#define mail_string m_strg
+#define mail_string_init mt_ini
+#define mail_string_next mt_nxt
+#define mail_string_setpos mt_sps
+#define mail_strip_subject mst_sb
+#define mail_strip_subject_wsp mst_ws
+#define mail_strip_subject_blob mst_bl
+#define mail_subscribe m_sub
+#define mail_thread m_thr
+#define mail_threadlist mt_lst
+#define mail_thread_c2node mt_c2n
+#define mail_thread_check_child mt_ckc
+#define mail_thread_compare_date mtc_da
+#define mail_thread_loadcache mt_ldc
+#define mail_thread_msgs mt_mgs
+#define mail_thread_orderedsubject mt_osb
+#define mail_thread_parse_msgid mtp_mi
+#define mail_thread_parse_references mtp_rf
+#define mail_thread_prune_dummy mt_prd
+#define mail_thread_references mt_ref
+#define mail_thread_sort mt_srt
+#define mail_uid m_uid
+#define mail_uid_sequence mu_seq
+#define mail_unlock m_unl
+#define mail_unsubscribe m_uns
+#define mail_usable_network_stream m_usns
+#define mail_utf7_valid m_ut7v
+#define mail_valid m_val
+#define mail_valid_net mv_net
+#define mail_valid_net_parse mvn_pr
+#define mail_valid_net_parse_work mvn_pw
+#define mail_versioncheck m_vers
+#define mailboxfile mbxfil
+#define md5_init m5_ini
+#define md5_update m5_upd
+#define md5_final m5_fin
+#define mime2_decode m2_dec
+#define mime2_text m2_txt
+#define mime2_token m2_tok
+#define mm_cache mm_cac
+#define mm_critical mm_crt
+#define mm_diskerror mm_dse
+#define mm_dlog mm_dlg
+#define mm_exists mm_exs
+#define mm_expunged mm_exp
+#define mm_fatal mm_ftl
+#define mm_flags mm_flg
+#define mm_list mm_lst
+#define mm_log mm_log
+#define mm_login mm_lgi
+#define mm_lsub mm_lsb
+#define mm_mailbox mm_mbx
+#define mm_nocritical mm_ncr
+#define mm_notify mm_not
+#define mm_searched mm_src
+#define myhomedir myhome
+#define mylocalhost myhost
+#define myusername_full myuser
+#define net_aopen nt_aop
+#define net_close nt_cls
+#define net_getbuffer nt_gtb
+#define net_getdata nt_gtd
+#define net_getline nt_gtl
+#define net_host nt_hst
+#define net_localhost nt_lhs
+#define net_open nt_opn
+#define net_port nt_prt
+#define net_sout nt_sot
+#define net_soutr nt_str
+#define netmsg_read nm_rea
+#define netmsg_slurp nm_slr
+#define netmsg_slurp_text nm_slt
+#define newsrc_check_uid nsc_ui
+#define newsrc_create ns_crea
+#define newsrc_error ns_err
+#define newsrc_lsub ns_lsub
+#define newsrc_newmessages ns_nms
+#define newsrc_newstate ns_nst
+#define newsrc_read ns_rea
+#define newsrc_status ns_sta
+#define newsrc_update ns_upd
+#define newsrc_write ns_wri
+#define newsrc_write_error ns_wer
+#define nntp_append n_appn
+#define nntp_canonicalize n_cano
+#define nntp_check n_chck
+#define nntp_close n_clos
+#define nntp_copy n_copy
+#define nntp_create n_crea
+#define nntp_delete n_del
+#define nntp_expunge n_expn
+#define nntp_fake n_fake
+#define nntp_fetchfast nf_fst
+#define nntp_fetchflags nf_flg
+#define nntp_fetchmessage nf_msg
+#define nntp_flagmsg n_fmsg
+#define nntp_gc n_gc
+#define nntp_getmap n_gmap
+#define nntp_header n_head
+#define nntp_isvalid n_isvl
+#define nntp_list n_list
+#define nntp_lsub n_lsub
+#define nntp_mail n_mail
+#define nntp_mclose n_mcls
+#define nntp_mopen n_mopn
+#define nntp_open_full n_open
+#define nntp_over n_ovr
+#define nntp_overview n_over
+#define nntp_parameters n_parm
+#define nntp_parsestructure n_pars
+#define nntp_parse_overview n_povr
+#define nntp_ping n_ping
+#define nntp_rename n_ren
+#define nntp_reply n_repl
+#define nntp_scan n_scan
+#define nntp_search n_srch
+#define nntp_search_msg ns_msg
+#define nntp_send n_send
+#define nntp_send_auth ns_aut
+#define nntp_send_auth_work ns_atw
+#define nntp_send_work n_sndw
+#define nntp_sort n_sort
+#define nntp_sort_loadcache ns_lcs
+#define nntp_soutr n_sout
+#define nntp_status n_stat
+#define nntp_subscribe n_sub
+#define nntp_text n_text
+#define nntp_text_slurp nt_slp
+#define nntp_thread n_thrd
+#define nntp_unsubscribe n_uns
+#define nntp_valid n_val
+#define pop3_append p_appn
+#define pop3_auth p_auth
+#define pop3_cache p_cach
+#define pop3_challenge p_chal
+#define pop3_check p_chck
+#define pop3_close p_clos
+#define pop3_copy p_copy
+#define pop3_create p_crea
+#define pop3_delete p_del
+#define pop3_expunge p_exp
+#define pop3_fake p_fake
+#define pop3_fetchfast pf_fst
+#define pop3_fetchflags pf_flg
+#define pop3_fetchmessage pf_msg
+#define pop3_gc p_gc
+#define pop3_list p_list
+#define pop3_lsub p_lsub
+#define pop3_open p_open
+#define pop3_parameters p_parm
+#define pop3_parsestructure p_pars
+#define pop3_ping p_ping
+#define pop3_rename p_ren
+#define pop3_reply p_rep
+#define pop3_response p_resp
+#define pop3_scan p_scan
+#define pop3_send p_send
+#define pop3_send_num ps_num
+#define pop3_status p_stat
+#define pop3_subscribe p_sub
+#define pop3_unsubscribe p_uns
+#define pop3_valid p_val
+#define rfc822_8bit r
+#define rfc822_address r_addr
+#define rfc822_address_line ra_lin
+#define rfc822_base64 r_b64
+#define rfc822_binary r_bin
+#define rfc822_cat r_cat
+#define rfc822_contents r_cont
+#define rfc822_cpy r_cpy
+#define rfc822_cpy_adr rc_adr
+#define rfc822_date r_date
+#define rfc822_default_subtype rd_sub
+#define rfc822_encode_body_7bit reb_7b
+#define rfc822_encode_body_8bit reb_8b
+#define rfc822_header r_head
+#define rfc822_header_line rh_lin
+#define rfc822_output r_out
+#define rfc822_output_address ro_adr
+#define rfc822_output_address_line roa_ln
+#define rfc822_output_address_list roa_li
+#define rfc822_output_body ro_bdy
+#define rfc822_output_body_header rob_hd
+#define rfc822_output_full ro_ful
+#define rfc822_output_flush ro_flu
+#define rfc822_output_header ro_hdr
+#define rfc822_output_header_line roh_ln
+#define rfc822_output_cat ro_cat
+#define rfc822_output_parameter ro_par
+#define rfc822_output_stringlist ro_stl
+#define rfc822_output_text ro_txt
+#define rfc822_parse_address rp_adr
+#define rfc822_parse_addrspec rp_ads
+#define rfc822_parse_adrlist rp_adl
+#define rfc822_parse_content rp_cnt
+#define rfc822_parse_content_header rpc_hd
+#define rfc822_parse_group rp_grp
+#define rfc822_parse_mailbox rp_mbx
+#define rfc822_parse_msg_full rp_msg
+#define rfc822_parse_parameter rp_par
+#define rfc822_parse_phrase rp_phr
+#define rfc822_parse_routeaddr rp_rte
+#define rfc822_parse_word rp_wrd
+#define rfc822_phraseonly r_poly
+#define rfc822_qprint r_qpnt
+#define rfc822_quote r_quot
+#define rfc822_skip_comment rs_cmt
+#define rfc822_skipws rs_ws
+#define rfc822_timezone r_tz
+#define rfc822_write_address_full rw_adr
+#define rfc822_write_body_header rwbh_8
+#define server_input_wait s_iwat
+#define server_login s_log
+#define server_init s_init
+#define sm_read sm_rd
+#define sm_subscribe sm_sub
+#define sm_unsubscribe sm_uns
+#define smtp_auth s_auth
+#define smtp_challenge s_chal
+#define smtp_close s_clos
+#define smtp_ehlo s_ehlo
+#define smtp_fake s_fake
+#define smtp_mail s_mail
+#define smtp_open_full s_open
+#define smtp_rcpt s_rcpt
+#define smtp_reply s_repl
+#define smtp_response s_resp
+#define smtp_send s_send
+#define smtp_send_auth ss_aut
+#define smtp_send_auth_work ss_atw
+#define smtp_send_work ss_wrk
+#define smtp_soutr s_str
+#define strcrlfcpy sc_cpy
+#define strcrlflen sc_len
+#define tcp_aopen t_aopn
+#define tcp_canonical t_cnon
+#define tcp_clientaddr t_cadr
+#define tcp_clienthost t_chst
+#define tcp_clientport t_cprt
+#define tcp_close t_clos
+#define tcp_getbuffer tg_buf
+#define tcp_getdata tg_dat
+#define tcp_getline tg_lin
+#define tcp_host t_host
+#define tcp_localhost t_lhst
+#define tcp_open t_open
+#define tcp_parameters t_parameters
+#define tcp_port t_port
+#define tcp_remotehost t_rhst
+#define tcp_serveraddr t_sadr
+#define tcp_serverhost t_shst
+#define tcp_serverport t_sprt
+#define tcp_sout t_sout
+#define tcp_soutr t_str
+#define textcpy txcopy
+#define textcpystring txcpst
+#define textcpyoffstring txcpos
+#define ucs4_cs_get u4_csg
+#define ucs4_decompose u4_dcm
+#define ucs4_decompose_recursive u4_dcr
+#define ucs4_rmapbuf u4r_bf
+#define ucs4_rmaplen u4r_ln
+#define ucs4_rmaptext u4r_tx
+#define ucs4_titlecase u4_tcs
+#define ucs4_width u4_wid
+#define utf8_badcharset u8_bcs
+#define utf8_charset u8_chs
+#define utf8_cstext u8_cst
+#define utf8_cstocstext u8_cct
+#define utf8_from_mutf7 u8fmu7
+#define utf8_get u8_get
+#define utf8_get_raw u8_gtr
+#define utf8_iso2022text u8_i22
+#define utf8_mime2text u8_mi2
+#define utf8_put u8_put
+#define utf8_rmap u8_rmp
+#define utf8_rmap_cs u8r_cs
+#define utf8_rmap_gen u8r_gn
+#define utf8_rmapsize u8r_sz
+#define utf8_rmaptext u8r_tx
+#define utf8_script u8_scr
+#define utf8_searchpgm u8_spg
+#define utf8_size u8_siz
+#define utf8_stringlist u8_lst
+#define utf8_text u8_txt
+#define utf8_text_2022 u8t_22
+#define utf8_text_8859_1 u8t_we
+#define utf8_text_1byte0 u8t_10
+#define utf8_text_1byte u8t_1b
+#define utf8_text_1byte8 u8t_18
+#define utf8_text_cs ut8_cs
+#define utf8_text_euc u8t_eu
+#define utf8_text_dbyte u8t_db
+#define utf8_text_dbyte2 u8t_d2
+#define utf8_text_sjis u8t_sj
+#define utf8_text_ucs2 u8t_u2
+#define utf8_text_ucs4 ut8_u4
+#define utf8_text_utf7 ut8_u7
+#define utf8_text_utf8 ut8_u8
+#define utf8_text_utf16 ut8_16
+#define utf8_to_mutf7 u8tmu7
+#define utf8_validate u8_val
+#define utf8_textwidth u8_twd
diff --git a/imap/src/osdep/tops-20/tcp_t20.c b/imap/src/osdep/tops-20/tcp_t20.c
new file mode 100644
index 00000000..7b2a2580
--- /dev/null
+++ b/imap/src/osdep/tops-20/tcp_t20.c
@@ -0,0 +1,365 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: TOPS-20 TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 13 January 2008
+ */
+
+
+/* Dedication:
+ * This file is dedicated with affection to the TOPS-20 operating system, which
+ * set standards for user and programmer friendliness that have still not been
+ * equaled by more `modern' operating systems.
+ * Wasureru mon ka!!!!
+ */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd);
+
+/* TCP/IP manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tcp_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* TCP/IP open
+ * Accepts: host name
+ * contact service name
+ * contact port number
+ * Returns: TCP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_open (char *host,char *service,unsigned long port)
+{
+ char *s,tmp[MAILTMPLEN];
+ TCPSTREAM *stream = NIL;
+ int argblk[5],jfn;
+ unsigned long i,j,k,l;
+ char file[MAILTMPLEN];
+ port &= 0xffff; /* erase flags */
+ /* domain literal? */
+ if (host[0] == '[' && host[strlen (host)-1] == ']') {
+ if (((i = strtoul (s = host+1,&s,10)) <= 255) && *s++ == '.' &&
+ ((j = strtoul (s,&s,10)) <= 255) && *s++ == '.' &&
+ ((k = strtoul (s,&s,10)) <= 255) && *s++ == '.' &&
+ ((l = strtoul (s,&s,10)) <= 255) && *s++ == ']' && !*s) {
+ argblk[3] = (i << 24) + (j << 16) + (k << 8) + l;
+ sprintf (tmp,"[%lu.%lu.%lu.%lu]",i,j,k,l);
+ }
+ else {
+ sprintf (tmp,"Bad format domain-literal: %.80s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ }
+ else { /* host name */
+ argblk[1] = _GTHPN; /* get IP address and primary name */
+ argblk[2] = (int) (host-1); /* pointer to host */
+ argblk[4] = (int) (tmp-1);
+ if (!jsys (GTHST,argblk)) { /* first try DEC's domain way */
+ argblk[1] = _GTHPN; /* get IP address and primary name */
+ argblk[2] = (int) (host-1);
+ argblk[4] = (int) (tmp-1);
+ if (!jsys (GTDOM,argblk)){/* try the CHIVES domain way */
+ argblk[1] = _GTHSN; /* failed, do the host table then */
+ if (!jsys (GTHST,argblk)) {
+ sprintf (tmp,"No such host as %s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ argblk[1] = _GTHNS; /* convert number to string */
+ argblk[2] = (int) (tmp-1);
+ /* get the official name */
+ if (!jsys (GTHST,argblk)) strcpy (tmp,host);
+ }
+ }
+ }
+
+ sprintf (file,"TCP:.%o-%d;PERSIST:30;CONNECTION:ACTIVE",argblk[3],port);
+ argblk[1] = GJ_SHT; /* short form GTJFN% */
+ argblk[2] = (int) (file-1); /* pointer to file name */
+ /* get JFN for TCP: file */
+ if (!jsys (GTJFN,argblk)) fatal ("Unable to create TCP JFN");
+ jfn = argblk[1]; /* note JFN for later */
+ /* want 8-bit bidirectional I/O */
+ argblk[2] = OF_RD|OF_WR|(FLD (8,monsym("OF%BSZ")));
+ if (!jsys (OPENF,argblk)) {
+ sprintf (file,"Can't connect to %s,%d server",tmp,port);
+ mm_log (file,ERROR);
+ return NIL;
+ }
+ /* create TCP/IP stream */
+ stream = (TCPSTREAM *) fs_get (sizeof (TCPSTREAM));
+ stream->host = cpystr (tmp); /* copy official host name */
+ argblk[1] = _GTHNS; /* convert number to string */
+ argblk[2] = (int) (tmp-1);
+ argblk[3] = -1; /* want local host */
+ if (!jsys (GTHST,argblk)) strcpy (tmp,"LOCAL");
+ stream->localhost = cpystr (tmp);
+ stream->port = port; /* save port number */
+ stream->jfn = jfn; /* init JFN */
+ return stream;
+}
+
+/* TCP/IP authenticated open
+ * Accepts: NETMBX specifier
+ * service name
+ * returned user name buffer
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL;
+}
+
+/* TCP receive line
+ * Accepts: TCP stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *tcp_getline (TCPSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = tcp_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = tcp_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* TCP receive line or partial line
+ * Accepts: TCP stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ int argblk[5];
+ unsigned long n,m;
+ char *ret,*stp,*st;
+ *contd = NIL; /* assume no continuation */
+ argblk[1] = stream->jfn; /* read from TCP */
+ /* pointer to buffer */
+ argblk[2] = (int) (stream->ibuf - 1);
+ argblk[3] = BUFLEN; /* max number of bytes to read */
+ argblk[4] = '\012'; /* terminate on LF */
+ if (!jsys (SIN,argblk)) return NIL;
+ n = BUFLEN - argblk[3]; /* number of bytes read */
+ /* got a complete line? */
+ if ((stream->ibuf[n - 2] == '\015') && (stream->ibuf[n - 1] == '\012')) {
+ memcpy ((ret = (char *) fs_get (n)),stream->ibuf,*size = n - 2);
+ ret[n - 2] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ /* copy partial string */
+ memcpy ((ret = (char *) fs_get (n)),stream->ibuf,*size = n);
+ /* special case of newline broken by buffer */
+ if ((stream->ibuf[n - 1] == '\015') && jsys (BIN,argblk) &&
+ (argblk[2] == '\012')) { /* was it? */
+ ret[n - 1] = '\0'; /* tie off string with null */
+ }
+ /* otherwise back up */
+ else if (!jsys (BKJFN,argblk)) fs_give ((void **) &ret);
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* TCP/IP receive buffer
+ * Accepts: TCP/IP stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *buffer)
+{
+ int argblk[5];
+ argblk[1] = stream->jfn; /* read from TCP */
+ argblk[2] = (int) (buffer-1); /* pointer to buffer */
+ argblk[3] = -size; /* number of bytes to read */
+ if (!jsys (SIN,argblk)) return NIL;
+ buffer[size] = '\0'; /* tie off text */
+ return T;
+}
+
+
+/* TCP/IP send string as record
+ * Accepts: TCP/IP stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long tcp_soutr (TCPSTREAM *stream,char *string)
+{
+ int argblk[5];
+ argblk[1] = stream->jfn; /* write to TCP */
+ argblk[2] = (int) (string-1); /* pointer to buffer */
+ argblk[3] = 0; /* write until NUL */
+ if (!jsys (SOUTR,argblk)) return NIL;
+ return T;
+}
+
+
+/* TCP/IP send string
+ * Accepts: TCP/IP stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
+{
+ int argblk[5];
+ argblk[1] = stream->jfn; /* write to TCP */
+ argblk[2] = (int) (string-1); /* pointer to buffer */
+ argblk[3] = -size; /* write this many bytes */
+ if (!jsys (SOUTR,argblk)) return NIL;
+ return T;
+}
+
+/* TCP/IP close
+ * Accepts: TCP/IP stream
+ */
+
+void tcp_close (TCPSTREAM *stream)
+{
+ int argblk[5];
+ argblk[1] = stream->jfn; /* close TCP */
+ jsys (CLOSF,argblk);
+ /* flush host names */
+ fs_give ((void **) &stream->host);
+ fs_give ((void **) &stream->localhost);
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* TCP/IP return host for this stream
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_host (TCPSTREAM *stream)
+{
+ return stream->host; /* return host name */
+}
+
+
+/* TCP/IP return remote host for this stream
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_remotehost (TCPSTREAM *stream)
+{
+ return stream->host; /* return host name */
+}
+
+
+/* TCP/IP return port for this stream
+ * Accepts: TCP/IP stream
+ * Returns: port number for this stream
+ */
+
+unsigned long tcp_port (TCPSTREAM *stream)
+{
+ return stream->port; /* return port number */
+}
+
+
+/* TCP/IP return local host for this stream
+ * Accepts: TCP/IP stream
+ * Returns: local host name for this stream
+ */
+
+char *tcp_localhost (TCPSTREAM *stream)
+{
+ return stream->localhost; /* return local host name */
+}
+
+/* TCP/IP return canonical form of host name
+ * Accepts: host name
+ * Returns: canonical form of host name
+ */
+
+char *tcp_canonical (char *name)
+{
+ int argblk[5];
+ static char tmp[MAILTMPLEN];
+ /* look like domain literal? */
+ if (name[0] == '[' && name[strlen (name) - 1] == ']') return name;
+ argblk[1] = _GTHPN; /* get IP address and primary name */
+ argblk[2] = (int) (name-1); /* pointer to host */
+ argblk[4] = (int) (tmp-1); /* pointer to return destination */
+ if (!jsys (GTHST,argblk)) { /* first try DEC's domain way */
+ argblk[1] = _GTHPN; /* get IP address and primary name */
+ argblk[2] = (int) (name-1);
+ argblk[4] = (int) (tmp-1);
+ if (!jsys (GTDOM,argblk)) { /* try the CHIVES domain way */
+ argblk[1] = _GTHSN; /* failed, do the host table then */
+ if (!jsys (GTHST,argblk)) return name;
+ argblk[1] = _GTHNS; /* convert number to string */
+ argblk[2] = (int) (tmp-1);
+ /* get the official name */
+ if (!jsys (GTHST,argblk)) return name;
+ }
+ }
+ return tmp;
+}
+
+
+/* TCP/IP get client host name (server calls only)
+ * Returns: client host name
+ */
+
+char *tcp_clienthost ()
+{
+ return "UNKNOWN";
+}
diff --git a/imap/src/osdep/tops-20/tcp_t20.h b/imap/src/osdep/tops-20/tcp_t20.h
new file mode 100644
index 00000000..291465cf
--- /dev/null
+++ b/imap/src/osdep/tops-20/tcp_t20.h
@@ -0,0 +1,65 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: TOPS-20 TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Dedication:
+ * This file is dedicated with affection to the TOPS-20 operating system, which
+ * set standards for user and programmer friendliness that have still not been
+ * equaled by more `modern' operating systems.
+ * Wasureru mon ka!!!!
+ */
+
+/* TCP input buffer */
+
+#define BUFLEN 8192
+
+/* TCP I/O stream (must be before osdep.h is included) */
+
+#define TCPSTREAM struct tcp_stream
+TCPSTREAM {
+ char *host; /* host name */
+ unsigned long port; /* port number */
+ char *localhost; /* local host name */
+ int jfn; /* jfn for connection */
+ char ibuf[BUFLEN]; /* input buffer */
+};
+
+
+/* All of these should be in JSYS.H, but just in case... */
+
+#ifndef _GTHPN
+#define _GTHPN 12
+#endif
+
+#ifndef _GTDPN
+#define _GTDPN 12
+#endif
+
+#ifndef GTDOM
+#define GTDOM (FLD (1,JSYS_CLASS) | 501
+#endif
diff --git a/imap/src/osdep/unix/Makefile b/imap/src/osdep/unix/Makefile
new file mode 100644
index 00000000..78913acc
--- /dev/null
+++ b/imap/src/osdep/unix/Makefile
@@ -0,0 +1,1078 @@
+# ========================================================================
+# Copyright 1988-2007 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# ========================================================================
+
+# Program: C client makefile
+#
+# Author: Mark Crispin
+# Networks and Distributed Computing
+# Computing & Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, WA 98195
+# Internet: MRC@CAC.Washington.EDU
+#
+# Date: 11 May 1989
+# Last Edited: 17 December 2007
+
+
+# Command line build parameters
+
+EXTRAAUTHENTICATORS=
+EXTRADRIVERS=mbox
+PASSWDTYPE=std
+SSLTYPE=nopwd
+IP=4
+
+
+# The optimization level here for GCC ports is set here for a reason. It's
+# to get you to read this text.
+# The general concensus seems to be that -O2 is the one to use.
+# Over the years, I've been told to use many different settings, including -O6.
+# In recent versions of GCC [as of 2/2005], -O6 generates bad code that, among
+# other ill effects, causes infinite loops.
+# -O3 seems to be safe, but empirical observation from our local expert
+# indicates that in some (many?) cases -O3 code runs slower than -O2.
+
+GCCOPTLEVEL= -O2
+
+
+# Try to have some consistency in GCC builds. We want optimization, but we
+# also want to be able to debug.
+
+GCCCFLAGS= -g $(GCCOPTLEVEL) -pipe -fno-omit-frame-pointer
+GCC4CFLAGS= $(GCCCFLAGS) -Wno-pointer-sign
+
+
+# Extended flags needed for SSL. You may need to modify.
+
+SSLDIR=/usr/local/ssl
+SSLCERTS=$(SSLDIR)/certs
+SSLKEYS=$(SSLCERTS)
+SSLINCLUDE=$(SSLDIR)/include
+SSLLIB=$(SSLDIR)/lib
+
+SSLCRYPTO=-lcrypto
+
+# Older versions of MIT Kerberos also have a libcrypto. If so, you may need
+# to use this instead
+#SSLCRYPTO=$(SSLLIB)/libcrypto.a
+
+# RSA Security Inc. released the RSA public key encryption algorithm into
+# the public domain on September 6, 2000. There is no longer any need to
+# use RSAREF.
+SSLRSA= # -lRSAglue -lrsaref
+
+SSLCFLAGS= -I$(SSLINCLUDE) -I$(SSLINCLUDE)/openssl\
+ -DSSL_CERT_DIRECTORY=\"$(SSLCERTS)\" -DSSL_KEY_DIRECTORY=\"$(SSLKEYS)\"
+SSLLDFLAGS= -L$(SSLLIB) -lssl $(SSLCRYPTO) $(SSLRSA)
+
+
+# Extended flags needed for non-standard passwd types. You may need to modify.
+
+AFSDIR=/usr/afsws
+AFSCFLAGS=-I$(AFSDIR)/include
+AFSLIB=$(AFSDIR)/lib
+AFSLDFLAGS=-L$(AFSLIB)/afs -L$(AFSLIB) -L$(AFSDIR)/domestic/lib\
+ -lkauth -lprot -lubik -lauth -lrxkad -lrx -llwp -ldes -lcom_err\
+ $(AFSLIB)/afs/util.a -laudit -lsys
+# AFSLDFLAGS may also need -L/usr/ucblib -lucb
+DCECFLAGS= -DDCE_MINIMAL -DPASSWD_OVERRIDE=\"/opt/pop3/passwd/passwd\"
+DCELDFLAGS= -ldce
+PAMLDFLAGS= -lpam -ldl
+
+
+# Build parameters normally set by the individual port
+
+CHECKPW=std
+LOGINPW=std
+SIGTYPE=bsd
+CRXTYPE=std
+ACTIVEFILE=/usr/lib/news/active
+SPOOLDIR=/usr/spool
+MAILSPOOL=$(SPOOLDIR)/mail
+NEWSSPOOL=$(SPOOLDIR)/news
+RSHPATH=/usr/ucb/rsh
+MD5PWD=/etc/cram-md5.pwd
+# Tries one of the test alternatives below if not specified.
+LOCKPGM=
+# Test alternatives if LOCKPGM not specified
+LOCKPGM1=/usr/libexec/mlock
+LOCKPGM2=/usr/sbin/mlock
+LOCKPGM3=/etc/mlock
+
+
+# Default formats for creating new mailboxes and for empty mailboxes in the
+# default namespace; must be set to the associated driver's prototype.
+#
+# The CREATEPROTO is the default format for new mailbox creation.
+# The EMPTYPROTO is the default format for handling zero-byte files.
+#
+# Normally, this is set by the individual port.
+#
+# NOTE: namespace formats (e.g. mh and news) can not be set as a default format
+# since they do not exist in the default namespace. Also, it is meaningless to
+# set certain other formats (e.g. mbx, mx, and mix) as the EMPTYPROTO since
+# these formats can never be empty files.
+
+CREATEPROTO=unixproto
+EMPTYPROTO=unixproto
+
+
+# Commands possibly overriden by the individual port
+
+ARRC=ar rc
+CC=cc
+LN=ln -s
+RANLIB=ranlib
+
+
+# Standard distribution build parameters
+
+DEFAULTAUTHENTICATORS=ext md5 pla log
+#
+# mh needs to be after any other directory format drivers (such as mx or mix)
+# since otherwise mh will seize any directory that is under the mh path.
+# However, mh needs to be before any sysinbox formats (such as mmdf or unix)
+# since otherwise INBOX won't work correctly when mh_allow_inbox is set.
+#
+DEFAULTDRIVERS=imap nntp pop3 mix mx mbx tenex mtx mh mmdf unix news phile
+CHUNKSIZE=65536
+
+# Normally no need to change any of these
+
+ARCHIVE=c-client.a
+BINARIES=osdep.o mail.o misc.o newsrc.o smanager.o utf8.o utf8aux.o siglocal.o \
+ dummy.o pseudo.o netmsg.o flstring.o fdstring.o \
+ rfc822.o nntp.o smtp.o imap4r1.o pop3.o \
+ unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o mix.o
+CFLAGS=-g
+
+CAT=cat
+MAKE=make
+MV=mv
+RM=rm -rf
+SH=sh
+
+
+# Primary build command
+
+BUILD=$(MAKE) build EXTRACFLAGS='$(EXTRACFLAGS)'\
+ EXTRALDFLAGS='$(EXTRALDFLAGS)'\
+ EXTRADRIVERS='$(EXTRADRIVERS)' EXTRAAUTHENTICATORS='$(EXTRAAUTHENTICATORS)'\
+ PASSWDTYPE=$(PASSWDTYPE) SSLTYPE=$(SSLTYPE) IP=$(IP)
+
+
+# Here if no make argument established
+
+missing: osdep.h
+ $(MAKE) all `$(CAT) SPECIALS`
+
+osdep.h:
+ @echo You must specify what type of system
+ @false
+
+
+# Current ports
+
+a32: # AIX 3.2 for RS/6000
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CRXTYPE=nfs \
+ SPOOLDIR=/var/spool \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="-g -Dunix=1 -D_BSD" \
+ BASELDFLAGS="-lbsd"
+
+a41: # AIX 4.1 for RS/6000
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CHECKPW=a41 CRXTYPE=nfs \
+ SPOOLDIR=/var/spool \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="-g -Dunix=1 -D_BSD -qro -qroconst" \
+ BASELDFLAGS="-ls"
+
+a52: # Attempt at AIX 5.2
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CHECKPW=a41 CRXTYPE=nfs \
+ SPOOLDIR=/var/spool \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="-g -Dunix=1 -D_BSD -qro -qroconst" \
+ BASELDFLAGS="-ls"
+
+aix: # AIX/370
+ @echo You are building for AIX on an S/370 class machine
+ @echo If you want AIX on an RS/6000 you need to use a32 or a41 instead!
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ BASECFLAGS="-g" \
+ BASELDFLAGS="-lbsd"
+
+aos: # AOS for RT
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ BASECFLAGS="-g -Dconst="
+
+art: # AIX 2.2.1 for RT
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=sv4 \
+ SPOOLDIR=/var \
+ ACTIVEFILE=/usr/local/news/control/active \
+ RSHPATH=/bin/rsh \
+ BASECFLAGS="-g -Dconst= -Dvoid=char" \
+ RANLIB=true
+
+asv: # Altos SVR4
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=sv4 LOGINPW=old \
+ ACTIVEFILE=/usr/spool/news/active \
+ RSHPATH=/usr/bin/rcmd \
+ BASECFLAGS="-Dconst= -DSIGSTOP=SIGKILL" \
+ BASELDFLAGS="-lsocket -lrpc -lgen -lcrypt -lxenix" \
+ RANLIB=true
+
+aux: # A/UX
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ MAILSPOOL=/usr/mail \
+ BASECFLAGS="-g -B/usr/lib/big/ -Dvoid=char -Dconst=" \
+ RANLIB=true ARRC="ar -rc"
+
+bs3: # BSD/i386 3.0 or higher
+ $(BUILD) `$(CAT) SPECIALS` OS=bsi \
+ CHECKPW=bsi LOGINPW=bsi CRXTYPE=nfs \
+ SPOOLDIR=/var NEWSSPOOL=/var/news/spool \
+ ACTIVEFILE=/var/news/etc/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)" CC=shlicc
+
+bsd: # BSD UNIX
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ BASECFLAGS="-g -Dconst="
+
+bsf: # FreeBSD
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CRXTYPE=nfs \
+ SPOOLDIR=/var \
+ ACTIVEFILE=/usr/local/news/lib/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ BASELDFLAGS="-lcrypt"
+
+bsi: # BSD/i386
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ SPOOLDIR=/var NEWSSPOOL=/var/news/spool \
+ ACTIVEFILE=/var/news/etc/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)"
+
+bso: # OpenBSD
+ $(BUILD) `$(CAT) SPECIALS` OS=bsi \
+ SIGTYPE=psx CRXTYPE=nfs \
+ SPOOLDIR=/var \
+ ACTIVEFILE=/usr/local/news/lib/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)"
+
+cvx: # Convex
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ BASECFLAGS="-O -ext -Dconst="
+
+cyg: # Cygwin - note that most local file drivers don't work!!
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ DEFAULTDRIVERS="imap nntp pop3 mbx unix phile" \
+ SIGTYPE=psx CHECKPW=cyg LOGINPW=cyg CRXTYPE=std \
+ SPOOLDIR=/var \
+ ACTIVEFILE=/usr/local/news/lib/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ BASELDFLAGS="-lcrypt" \
+ CC=gcc
+
+d-g: # Data General DG/UX
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=sv4 CRXTYPE=nfs \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ ACTIVEFILE=/local/news/active \
+ RSHPATH=/usr/bin/remsh \
+ BASECFLAGS="-g -Dconst=" \
+ BASELDFLAGS="-lnsl -lsocket" \
+ RANLIB=true
+
+d54: # Data General DG/UX 5.4
+ $(BUILD) `$(CAT) SPECIALS` OS=d-g \
+ SIGTYPE=sv4 CRXTYPE=nfs \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ ACTIVEFILE=/local/news/active \
+ RSHPATH=/usr/bin/remsh \
+ BASECFLAGS="-g -Dconst=" \
+ BASELDFLAGS="-lnsl -lsocket" \
+ RANLIB=true
+
+dpx: # Bull DPX/2
+ $(BUILD) `$(CAT) SPECIALS` OS=sv4 \
+ SIGTYPE=sv4 CHECKPW=sv4 LOGINPW=sv4 \
+ RSHPATH=/usr/bin/remsh \
+ BASECFLAGS="-Dconst= -DSYSTEM5 -DSHORT_IDENT" \
+ BASELDFLAGS="-linet" \
+ RANLIB=true LN=ln
+
+drs: # ICL DRS/NX
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=sv4 CHECKPW=sv4 LOGINPW=sv4 CRXTYPE=nfs \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ ACTIVEFILE=/var/lib/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="-O" \
+ BASELDFLAGS="-lsocket -lgen" \
+ RANLIB=true
+
+do4: # Apollo Domain/OS sr10.4
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ BASECFLAGS="-A systype,bsd4.3 -D_APOLLO_SOURCE" \
+ RANLIB=true
+
+dyn: # Dynix
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ BASECFLAGS="-g -Dconst="
+
+epx: # EP/IX
+ $(BUILD) `$(CAT) SPECIALS` OS=sv4 \
+ SIGTYPE=sv4 CHECKPW=sv4 LOGINPW=sv4 \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ ACTIVEFILE=/usr/share/news/active \
+ RSHPATH=/usr/net/rsh \
+ BASECFLAGS="-g -systype svr4" \
+ BASELDFLAGS="-lsocket -lnsl -lgen" \
+ RANLIB=true
+
+ga4: # GCC AIX 4.1 for RS/6000
+ $(BUILD) `$(CAT) SPECIALS` OS=a41 \
+ SIGTYPE=psx CHECKPW=a41 CRXTYPE=nfs \
+ SPOOLDIR=/var/spool \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="-g -Dunix=1 -D_BSD" \
+ BASELDFLAGS="-ls"
+
+gas: # GCC Altos SVR4
+ $(BUILD) `$(CAT) SPECIALS` OS=asv \
+ SIGTYPE=sv4 LOGINPW=old \
+ ACTIVEFILE=/usr/spool/news/active \
+ RSHPATH=/usr/bin/rcmd \
+ BASECFLAGS="-g -O -DALTOS_SYSTEM_V -DSIGSTOP=SIGKILL" \
+ BASELDFLAGS="-lsocket -lrpc -lgen -lcrypt -lxenix" \
+ RANLIB=true CC=gcc
+
+gh9: # GCC HP-UX 9.x
+ $(BUILD) `$(CAT) SPECIALS` OS=hpp \
+ SIGTYPE=psx CRXTYPE=nfs \
+ MAILSPOOL=/usr/mail \
+ RSHPATH=/usr/bin/remsh \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ RANLIB=true CC=gcc
+
+ghp: # GCC HP-UX 10.x
+ $(BUILD) `$(CAT) SPECIALS` OS=hpp \
+ SIGTYPE=psx CRXTYPE=nfs \
+ SPOOLDIR=/var \
+ ACTIVEFILE=/var/news/active \
+ RSHPATH=/usr/bin/remsh \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ RANLIB=true CC=gcc
+
+ghs: # GCC HP-UX with Trusted Computer Base
+ $(BUILD) `$(CAT) SPECIALS` OS=shp \
+ SIGTYPE=psx CHECKPW=sec CRXTYPE=nfs \
+ SPOOLDIR=/var \
+ ACTIVEFILE=/var/news/active \
+ RSHPATH=/usr/bin/remsh \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ BASELDFLAGS="-lnet -lV3 -lsec" \
+ RANLIB=true CC=gcc
+
+go5: # GCC 2.7.1 (95q4) SCO Open Server 5.0.x
+ $(BUILD) `$(CAT) SPECIALS` OS=sc5 \
+ SIGTYPE=psx CHECKPW=sec LOGINPW=sec \
+ CREATEPROTO=mmdfproto EMPTYPROTO=mmdfproto \
+ SPOOLDIR=/var/spool \
+ ACTIVEFILE=/var/lib/news/active \
+ RSHPATH=/usr/bin/rcmd \
+ BASECFLAGS="$(GCCCFLAGS) -I/usr/include -L/lib" \
+ BASELDFLAGS="-lsocket -lprot -lx -ltinfo -lm" \
+ RANLIB=true CC=gcc
+
+gsc: # Santa Cruz Operation
+ $(BUILD) `$(CAT) SPECIALS` OS=sco \
+ SIGTYPE=sv4 CHECKPW=sec LOGINPW=sec \
+ CREATEPROTO=mmdfproto EMPTYPROTO=mmdfproto \
+ RSHPATH=/usr/bin/rcmd \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ BASELDFLAGS="-lsocket -lprot -lcrypt_i -lx -los" \
+ RANLIB=true LN=ln CC=gcc
+
+gsg: # GCC Silicon Graphics
+ $(BUILD) `$(CAT) SPECIALS` OS=sgi \
+ SIGTYPE=sv4 CRXTYPE=nfs \
+ MAILSPOOL=/usr/mail \
+ RSHPATH=/usr/bsd/rsh \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ RANLIB=true CC=gcc
+
+gso: os_sol.h # GCC Solaris
+ $(BUILD) `$(CAT) SPECIALS` OS=sol \
+ SIGTYPE=psx CHECKPW=psx CRXTYPE=nfs \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ ACTIVEFILE=/usr/share/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ BASELDFLAGS="-lsocket -lnsl -lgen" \
+ RANLIB=true CC=gcc
+
+gsu: # GCC SUN-OS
+ $(BUILD) `$(CAT) SPECIALS` OS=sun \
+ CRXTYPE=nfs \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ BASELDFLAGS="-ldl" \
+ CC=gcc
+
+gul: # GCC Ultrix
+ $(BUILD) `$(CAT) SPECIALS` OS=ult \
+ SIGTYPE=psx CHECKPW=ult CRXTYPE=nfs \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ BASELDFLAGS="-lauth -lc" \
+ CC=gcc
+
+h11: # HP-UX 11i
+ $(BUILD) `$(CAT) SPECIALS` OS=hpp \
+ SIGTYPE=psx CRXTYPE=nfs \
+ SPOOLDIR=/var \
+ ACTIVEFILE=/var/news/active \
+ RSHPATH=/usr/bin/remsh \
+ BASECFLAGS="-g -Ae" \
+ RANLIB=true
+
+hpp: # HP-UX 9.x
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CRXTYPE=nfs \
+ MAILSPOOL=/usr/mail \
+ RSHPATH=/usr/bin/remsh \
+ BASECFLAGS="-g -Aa -D_HPUX_SOURCE" \
+ BASELDFLAGS="-lnet -lV3" \
+ RANLIB=true
+
+hpx: # HP-UX 10.x
+ $(BUILD) `$(CAT) SPECIALS` OS=hpp \
+ SIGTYPE=psx CRXTYPE=nfs \
+ SPOOLDIR=/var \
+ ACTIVEFILE=/var/news/active \
+ RSHPATH=/usr/bin/remsh \
+ BASECFLAGS="-g -Ae" \
+ BASELDFLAGS="-lnet -lV3" \
+ RANLIB=true
+
+isc: # Interactive
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=sv4 CHECKPW=sv4 LOGINPW=sv4 \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ ACTIVEFILE=/var/spool/news/active \
+ BASECFLAGS="-Xp -D_SYSV3" \
+ BASELDFLAGS="-linet -lnsl_s -lgen -lx -lsec -liberty" \
+ RANLIB=true
+
+lnp: # Linux Pluggable Authentication modules
+ $(BUILD) `$(CAT) SPECIALS` OS=slx \
+ SIGTYPE=psx CHECKPW=pam CRXTYPE=nfs \
+ SPOOLDIR=/var/spool \
+ ACTIVEFILE=/var/lib/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ BASELDFLAGS="$(PAMLDFLAGS)"
+
+lnx: # Linux non-shadow passwords
+ @echo You are building for traditional Linux *without* shadow
+ @echo passwords and with the crypt function in the C library.
+ @echo If your system has shadow passwords, or if crypt is not
+ @echo in the C library, you must use slx, sl4, or sl5 instead!
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CRXTYPE=nfs \
+ SPOOLDIR=/var/spool \
+ ACTIVEFILE=/var/lib/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)"
+
+lyn: # LynxOS
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ RSHPATH=/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ BASELDFLAGS=-lbsd \
+ CC=gcc
+
+mct: # MachTen - CRXTYPE=nfs doesn't work (at least not on 2.2)
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SPOOLDIR=/var/spool \
+ BASECFLAGS="$(GCCCFLAGS)"
+
+mnt: # Mint
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CRXTYPE=nfs \
+ SPOOLDIR=/var/spool \
+ ACTIVEFILE=/var/lib/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)"
+
+neb: # NetBSD
+ $(BUILD) `$(CAT) SPECIALS` OS=bsi \
+ CRXTYPE=nfs \
+ SPOOLDIR=/var \
+ ACTIVEFILE=/var/db/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ BASELDFLAGS="-lcrypt"
+
+nec: # NEC UX
+ $(BUILD) `$(CAT) SPECIALS` OS=sv4 \
+ SIGTYPE=sv4 CHECKPW=sv4 \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ ACTIVEFILE=/var/news/lib/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="-g -Kopt=2 -KOlimit=2000" \
+ BASELDFLAGS="-lsocket -lnsl -lgen" \
+ RANLIB=true CC=/usr/abiccs/bin/cc
+
+nto: # QNX Neutrino RTP
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ SPOOLDIR=/var/spool \
+ ACTIVEFILE=/var/lib/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="-g -O"
+
+nxt: # NEXTSTEP
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ BASECFLAGS="$(GCCCFLAGS)"
+
+nx3: # NEXTSTEP 3.0 single threaded
+ $(BUILD) `$(CAT) SPECIALS` OS=nxt \
+ CRXTYPE=nfs \
+ BASECFLAGS="$(GCCCFLAGS)"
+ echo "void malloc_singlethreaded (void);" >> linkage.h
+ echo " malloc_singlethreaded ();" >> linkage.c
+
+osf: # OSF/1
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CRXTYPE=nfs \
+ SPOOLDIR=/var/spool \
+ BASECFLAGS="-g3 -w -O2 -Olimit 1500"
+
+# Note: sia_become_user() used by LOGINPW=os4 doesn't seem to work right. The
+# user doesn't get proper file access, and the process can't be killed.
+
+os4: # OSF/1 (Digital UNIX) 4
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CHECKPW=os4 LOGINPW=os4 CRXTYPE=nfs \
+ SPOOLDIR=/var/spool \
+ BASECFLAGS="-g3 -w -std0 -O2"
+
+osx: # Mac OS X
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCC4CFLAGS)"
+
+ptx: # PTX
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CHECKPW=svo LOGINPW=sv4 CRXTYPE=nfs \
+ MAILSPOOL=/usr/mail \
+ RSHPATH=/usr/bin/resh \
+ BASECFLAGS="-Wc,-O3 -Wc,-seq -Dprivate=PRIVATE" \
+ BASELDFLAGS="-lseq -lsec -lsocket -linet -lnsl -lgen" \
+ RANLIB=true
+
+pyr: # Pyramid
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ BASECFLAGS="-g -Dconst="
+
+qnx: # QNX
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CHECKPW=psx LOGINPW=old \
+ RSHPATH=/usr/ucb/rsh \
+ BASECFLAGS="-Otax -g -Dunix=1 -D_POSIX_SOURCE" \
+ BASELDFLAGS="-g -N128k -llogin -lsocket -lunix"
+
+s40: # SUN-OS 4.0
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ BASECFLAGS="-g -Dconst="
+
+sc5: # SCO Open Server 5.0
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CHECKPW=sec LOGINPW=sec \
+ CREATEPROTO=mmdfproto EMPTYPROTO=mmdfproto \
+ SPOOLDIR=/var/spool \
+ ACTIVEFILE=/var/lib/news/active \
+ RSHPATH=/usr/bin/rcmd \
+ BASECFLAGS="-O3 -s -belf" \
+ BASELDFLAGS="-lsocket -lprot -lx -ltinfo -lm" \
+ RANLIB=true
+
+sco: # Santa Cruz Operation
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=sv4 CHECKPW=sec LOGINPW=sec \
+ CREATEPROTO=mmdfproto EMPTYPROTO=mmdfproto \
+ RSHPATH=/usr/bin/rcmd \
+ BASECFLAGS="-O3" \
+ BASELDFLAGS="-lsocket -lprot -lcrypt_i -lx -los" \
+ RANLIB=true LN=ln
+
+# Note: setting _POSIX_SOURCE doesn't seem to build it as of SGI IRIX 5.3
+
+sgi: # Silicon Graphics
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=sv4 CRXTYPE=nfs \
+ MAILSPOOL=/usr/mail \
+ RSHPATH=/usr/bsd/rsh \
+ BASECFLAGS="-g3 -O2 -Olimit 8192" \
+ RANLIB=true
+
+sg6: # Silicon Graphics, IRIX 6.5
+ MAKEFLAGS= $(BUILD) `$(CAT) SPECIALS` OS=sgi \
+ SIGTYPE=sv4 CRXTYPE=nfs \
+ MAILSPOOL=/usr/mail \
+ RSHPATH=/usr/bsd/rsh \
+ BASECFLAGS="-g3 -O2 -OPT:Olimit=0 -woff 1110,1116" \
+ RANLIB=true
+
+# Note: Mark Kaesling says that setluid() isn't in HP-UX with SecureWare.
+
+shp: # HP-UX with Trusted Computer Base
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CHECKPW=sec CRXTYPE=nfs \
+ SPOOLDIR=/var \
+ ACTIVEFILE=/var/news/active \
+ RSHPATH=/usr/bin/remsh \
+ BASECFLAGS="-g -Ae" \
+ BASELDFLAGS="-lnet -lV3 -lsec" \
+ RANLIB=true
+
+slx: # Secure Linux
+ @echo You are building for libc6/glibc versions of Secure Linux
+ @echo If you want libc5 versions you must use sl5 instead!
+ @echo If you want libc4 versions you must use sl4 instead!
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CHECKPW=psx CRXTYPE=nfs \
+ SPOOLDIR=/var/spool \
+ ACTIVEFILE=/var/lib/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ BASELDFLAGS="-lcrypt"
+
+sl4: # Secure Linux using libc4
+ @echo You are building for libc4 versions of Secure Linux
+ @echo If you want libc6/glibc versions you must use slx instead!
+ @echo If you want libc5 versions you must use sl5 instead!
+ $(BUILD) `$(CAT) SPECIALS` OS=slx \
+ SIGTYPE=psx CHECKPW=psx CRXTYPE=nfs \
+ SPOOLDIR=/var/spool \
+ ACTIVEFILE=/var/lib/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)" \
+ BASELDFLAGS="-lshadow"
+
+sl5: # Secure Linux using libc5
+ @echo You are building for libc5 versions of Secure Linux
+ @echo If you want libc6/glibc versions you must use slx instead!
+ @echo If you want libc4 versions you must use sl4 instead!
+ $(BUILD) `$(CAT) SPECIALS` OS=slx \
+ SIGTYPE=psx CHECKPW=psx CRXTYPE=nfs \
+ SPOOLDIR=/var/spool \
+ ACTIVEFILE=/var/lib/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="$(GCCCFLAGS)"
+
+snx: # Siemens Nixdorf SINIX and Reliant UNIX
+ $(BUILD) `$(CAT) SPECIALS` OS=sv4 \
+ SIGTYPE=psx CHECKPW=sv4 \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ ACTIVEFILE=/usr/share/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="-g -D_SYS_CLOCK_H -Dconst=" \
+ BASELDFLAGS="-lsocket -lnsl -lgen" \
+ RANLIB=true
+
+# Sorry about the -w, but the cretinous SUN Workshop Pro C compiler barfs on
+# implicit casts between char and unsigned char.
+
+soc: os_sol.h # Solaris with cc
+ $(BUILD) `$(CAT) SPECIALS` OS=sol \
+ SIGTYPE=psx CHECKPW=psx CRXTYPE=nfs \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ ACTIVEFILE=/usr/share/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="-g -O -w" \
+ BASELDFLAGS="-lsocket -lnsl -lgen" \
+ RANLIB=true CC=/opt/SUNWspro/bin/cc
+
+
+# Note: It is a long and disgusting story about why cc is set to ucbcc. You
+# need to invoke the C compiler so that it links with the SVR4 libraries and
+# not the BSD libraries, otherwise readdir() will return the wrong information.
+# Of all the names in the most common path, ucbcc is the only name to be found
+# (on /usr/ccs/bin) that points to a suitable compiler. cc is likely to be
+# /usr/ucb/cc which is absolutely not the compiler that you want. The real
+# SVR4 cc is probably something like /opt/SUNWspro/bin/cc which is rarely in
+# anyone's path.
+#
+# ucbcc is probably a link to acc, e.g. /opt/SUNWspro/SC4.0/bin/acc, and is
+# the UCB C compiler using the SVR4 libraries.
+#
+# If ucbcc isn't on your system, then punt on the SUN C compiler and use gcc
+# instead (the gso port instead of the sol port).
+#
+# If, in spite of all the above warnings, you choose to use the "soc" port
+# instead of the "sol" port, be sure to check the behavior of the LIST command
+# in imapd. Also, note that the "soc" port uses -O. If you want to use the
+# real SVR4 compiler, you must use -O. If it works to compile with -O2, then
+# cc is probably using the UCB compiler with BSD libraries, and will not build
+# a good binary
+#
+# To recap:
+# 1) The sol port is designed to be built using the UCB compiler using the
+# SVR4 libraries. This compiler is "ucbcc", which is lunk to acc. You
+# use -O2 as one of the CFLAGS.
+# 2) If you build the sol port with the UCB compiler using the BSD libraries,
+# you will get no error messages but you will get bad binaries (the most
+# obvious symptom is dropping the first two characters return filenames
+# from the imapd LIST command. This compiler also uses -O2, and is very
+# often what the user gets from "cc". BEWARE!!!
+# 3) If you build the sol port with the real SVR4 compiler, which is often
+# hidden away or unavailable on many systems, then you will get errors
+# from -O2 and you need to change that to -O. But you will get a good
+# binary. However, you should try it with -O2 first, to make sure that
+# you got this compiler and not the UCB compiler using BSD libraries.
+
+sol: os_sol.h # Solaris
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CHECKPW=psx CRXTYPE=nfs \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ ACTIVEFILE=/usr/share/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="-g -O2" \
+ BASELDFLAGS="-lsocket -lnsl -lgen" \
+ RANLIB=true CC=ucbcc
+
+sos: # Secure OSF/1
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CHECKPW=sce LOGINPW=sec CRXTYPE=nfs \
+ BASECFLAGS="-g3 -w -O2 -Olimit 1500" \
+ BASELDFLAGS="-lsecurity -laud"
+
+ssn: # Secure SUN-OS
+ $(BUILD) `$(CAT) SPECIALS` OS=sun \
+ CHECKPW=ssn CRXTYPE=nfs \
+ BASECFLAGS="-g -Dconst=" \
+ BASELDFLAGS="-ldl"
+
+sua: # Windows Vista Subsystem for UNIX Applications
+ $(BUILD) `$(CAT) SPECIALS` OS=sua \
+ SIGTYPE=psx CRXTYPE=nfs LOGINPW=old \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ ACTIVEFILE=/var/lib/news/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="-g -O2" \
+ BASELDFLAGS="-lcrypt"
+
+sun: # SUN-OS
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ BASECFLAGS="-g -Dconst=" \
+ BASELDFLAGS="-ldl"
+
+sv2: # SVR2
+ @echo You are being *very* optimistic!
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=sv4 LOGINPW=old \
+ MAILSPOOL=/usr/mail \
+ RSHPATH=/usr/bin/remsh \
+ BASECFLAGS="-Dconst= -DSYSTEM5 -DSHORT_IDENT -I/usr/ethernet/include" \
+ BASELDFLAGS="-lnet" \
+ RANLIB=true LN=ln
+
+sv4: # SVR4
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=sv4 CHECKPW=sv4 LOGINPW=sv4 \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ ACTIVEFILE=/usr/share/news/active \
+ RSHPATH=/usr/bin/resh \
+ BASECFLAGS="-g -Dconst=" \
+ BASELDFLAGS="-lsocket -lnsl -lgen" \
+ RANLIB=true
+
+ult: # Ultrix
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ SIGTYPE=psx CHECKPW=ult CRXTYPE=nfs \
+ BASECFLAGS="-g3 -O2 -Olimit 1500 -Dconst=" \
+ BASELDFLAGS="-lauth -lc"
+
+uw2: # UnixWare SVR4.2
+ $(BUILD) `$(CAT) SPECIALS` OS=sv4 \
+ SIGTYPE=sv4 CHECKPW=sv4 \
+ SPOOLDIR=/var/spool MAILSPOOL=/var/mail \
+ ACTIVEFILE=/var/news/lib/active \
+ RSHPATH=/usr/bin/rsh \
+ BASECFLAGS="-g" \
+ BASELDFLAGS="-lsocket -lnsl -lgen" \
+ RANLIB=true
+
+vul: # VAX Ultrix
+ $(BUILD) `$(CAT) SPECIALS` OS=ult \
+ SIGTYPE=psx CHECKPW=ult CRXTYPE=nfs \
+ BASECFLAGS="-O2 -Dconst=" \
+ BASELDFLAGS="-lauth -lc"
+
+vu2: # VAX Ultrix 2.3, etc.
+ $(BUILD) `$(CAT) SPECIALS` OS=$@ \
+ CRXTYPE=nfs \
+ BASECFLAGS="-O2 -Dconst= -Dvoid=char"
+
+
+# Build it!
+
+build: clean once $(ARCHIVE)
+
+all: $(ARCHIVE)
+
+$(ARCHIVE): $(BINARIES)
+ sh -c '$(RM) $(ARCHIVE) || true'
+ @$(CAT) ARCHIVE
+ @$(SH) ARCHIVE
+
+.c.o:
+ `$(CAT) CCTYPE` -c `$(CAT) CFLAGS` $*.c
+
+
+# Cleanup
+
+clean:
+ sh -c '$(RM) auths.c crexcl.c ip_unix.c linkage.[ch] siglocal.c osdep*.[ch] *.o ARCHIVE *FLAGS *TYPE $(ARCHIVE) || true'
+
+
+# Dependencies
+
+dummy.o: mail.h misc.h osdep.h dummy.h
+fdstring.o: mail.h misc.h osdep.h fdstring.h
+flstring.o: mail.h misc.h osdep.h flstring.h
+imap4r1.o: mail.h misc.h osdep.h imap4r1.h rfc822.h
+mail.o: mail.h misc.h osdep.h rfc822.h linkage.h
+mbx.o: mail.h misc.h osdep.h dummy.h
+mh.o: mail.h misc.h osdep.h dummy.h
+mix.o: mail.h misc.h osdep.h dummy.h
+mx.o: mail.h misc.h osdep.h dummy.h
+misc.o: mail.h misc.h osdep.h
+mmdf.o: mail.h misc.h osdep.h pseudo.h dummy.h
+mtx.o: mail.h misc.h osdep.h dummy.h
+netmsg.o: mail.h misc.h osdep.h netmsg.h
+news.o: mail.h misc.h osdep.h
+newsrc.o: mail.h misc.h osdep.h newsrc.h
+nntp.o: mail.h misc.h osdep.h netmsg.h smtp.h nntp.h rfc822.h
+phile.o: mail.h misc.h osdep.h rfc822.h dummy.h
+pseudo.o: pseudo.h
+pop3.o: mail.h misc.h osdep.h rfc822.h
+smanager.o: mail.h misc.h osdep.h
+smtp.o: mail.h misc.h osdep.h smtp.h rfc822.h
+rfc822.o: mail.h misc.h osdep.h rfc822.h
+tenex.o: mail.h misc.h osdep.h dummy.h
+unix.o: mail.h misc.h osdep.h unix.h pseudo.h dummy.h
+utf8.o: mail.h misc.h osdep.h utf8.h tmap.c widths.c
+utf8aux.o: mail.h misc.h osdep.h utf8.h
+
+
+# OS-dependent
+
+osdep.o:mail.h misc.h env.h fs.h ftl.h nl.h tcp.h \
+ osdep.h env_unix.h tcp_unix.h \
+ osdep.c env_unix.c fs_unix.c ftl_unix.c nl_unix.c tcp_unix.c ip_unix.c\
+ auths.c crexcl.c flockcyg.c flocklnx.c flocksim.c fsync.c \
+ gethstid.c getspnam.c \
+ gr_wait.c gr_wait4.c gr_waitp.c \
+ kerb_mit.c \
+ auth_ext.c auth_gss.c auth_log.c auth_md5.c auth_pla.c \
+ pmatch.c scandir.c setpgrp.c strerror.c truncate.c write.c \
+ memmove.c memmove2.c memset.c \
+ tz_bsd.c tz_nul.c tz_sv4.c \
+ write.c sslstdio.c \
+ strerror.c strpbrk.c strstr.c strtok.c strtoul.c \
+ OSCFLAGS
+ @echo Building OS-dependent module
+ @echo If you get No such file error messages for files x509.h, ssl.h,
+ @echo pem.h, buffer.h, bio.h, and crypto.h, that means that OpenSSL
+ @echo is not installed on your system. Either install OpenSSL first
+ @echo or build with command: make `$(CAT) OSTYPE` SSLTYPE=none
+ `$(CAT) CCTYPE` -c `$(CAT) CFLAGS` `$(CAT) OSCFLAGS` -c osdep.c
+
+osdep.c: osdepbas.c osdepckp.c osdeplog.c osdepssl.c
+ $(CAT) osdepbas.c osdepckp.c osdeplog.c osdepssl.c > osdep.c
+
+osdepbas.c:
+ @echo osdepbas.c not found...try make clean and new make
+ @false
+
+osdepckp.c:
+ @echo osdepckp.c not found...try make clean and new make
+ @false
+
+osdeplog.c:
+ @echo osdeplog.c not found...try make clean and new make
+ @false
+
+osdepssl.c:
+ @echo osdepssl.c not found...try make clean and new make
+ @false
+
+siglocal.c:
+ @echo siglocal.c not found...try make clean and new make
+ @false
+
+crexcl.c:
+ @echo crexcl.c not found...do make clean and new make
+ @false
+
+ip_unix.c:
+ @echo ip_unix.c not found...do make clean and new make
+ @false
+
+os_sol.h:
+ sh -c 'if [ -f /lib/libc.a ]; then (strings /lib/libc.a | grep getpassphrase > /dev/null) && $(LN) os_soln.h os_sol.h || $(LN) os_solo.h os_sol.h ; else $(LN) os_soln.h os_sol.h ; fi'
+
+
+# Once-only environment setup
+
+once: onceenv ckp$(PASSWDTYPE) ssl$(SSLTYPE) osdep.c
+
+onceenv:
+ @echo Once-only environment setup...
+ echo $(CC) > CCTYPE
+ echo $(BASECFLAGS) '$(EXTRACFLAGS)' -DCHUNKSIZE=$(CHUNKSIZE) > CFLAGS
+ echo -DCREATEPROTO=$(CREATEPROTO) -DEMPTYPROTO=$(EMPTYPROTO) \
+ -DMD5ENABLE=\"$(MD5PWD)\" -DMAILSPOOL=\"$(MAILSPOOL)\" \
+ -DANONYMOUSHOME=\"$(MAILSPOOL)/anonymous\" \
+ -DACTIVEFILE=\"$(ACTIVEFILE)\" -DNEWSSPOOL=\"$(NEWSSPOOL)\" \
+ -DRSHPATH=\"$(RSHPATH)\" -DLOCKPGM=\"$(LOCKPGM)\" \
+ -DLOCKPGM1=\"$(LOCKPGM1)\" -DLOCKPGM2=\"$(LOCKPGM2)\" \
+ -DLOCKPGM3=\"$(LOCKPGM3)\" > OSCFLAGS
+ echo $(BASELDFLAGS) $(EXTRALDFLAGS) > LDFLAGS
+ echo "$(ARRC) $(ARCHIVE) $(BINARIES);$(RANLIB) $(ARCHIVE)" > ARCHIVE
+ echo $(OS) > OSTYPE
+ ./drivers $(EXTRADRIVERS) $(DEFAULTDRIVERS) dummy
+ ./mkauths $(EXTRAAUTHENTICATORS) $(DEFAULTAUTHENTICATORS)
+ echo " mail_versioncheck (CCLIENTVERSION);" >> linkage.c
+ $(LN) os_$(OS).h osdep.h
+ $(LN) os_$(OS).c osdepbas.c
+ $(LN) log_$(LOGINPW).c osdeplog.c
+ $(LN) sig_$(SIGTYPE).c siglocal.c
+ $(LN) crx_$(CRXTYPE).c crexcl.c
+ $(LN) ip$(IP)_unix.c ip_unix.c
+ sh -c '(test $(OS) = sc5 -o $(OS) = sco -o ! -f /usr/include/sys/statvfs.h) && echo -DNOFSTATVFS >> OSCFLAGS || fgrep statvfs64 /usr/include/sys/statvfs.h > /dev/null || echo -DNOFSTATVFS64 >> OSCFLAGS'
+
+
+# Password checkers
+
+ckpafs: # AFS
+ @echo AFS password authentication
+ echo $(AFSCFLAGS) >> OSCFLAGS
+# echo $(AFSLDFLAGS) >> LDFLAGS
+# Note: Steve Roseman says that AFS libraries have to be lunk before SSL
+ echo $(AFSLDFLAGS) `$(CAT) LDFLAGS` > LDFLAGS.tmp
+ mv LDFLAGS.tmp LDFLAGS
+ $(LN) ckp_afs.c osdepckp.c
+
+ckpdce: # DCE
+ @echo DCE password authentication
+ echo $(DCECFLAGS) >> OSCFLAGS
+ echo $(DCELDFLAGS) >> LDFLAGS
+ $(LN) ckp_dce.c osdepckp.c
+
+ckpgss: # Kerberos V (must have gss EXTRAAUTHENTICATOR as well)
+ @echo Kerberos V password authentication
+ $(LN) ckp_gss.c osdepckp.c
+
+ckpnul: # NUL authenticator (disables all plaintext authentication)
+ @echo Plaintext authentication prohibited
+ echo " mail_parameters (NIL,SET_DISABLEPLAINTEXT,(void *) 1);" >> linkage.c
+ $(LN) ckp_nul.c osdepckp.c
+
+ckppam: # Pluggable Authentication Modules authenticator
+ @echo PAM password authentication
+ echo $(PAMLDFLAGS) >> LDFLAGS
+ $(LN) ckp_pam.c osdepckp.c
+
+ckppmb: # Broken (e.g. SUN) Pluggable Authentication Modules authenticator
+ @echo Broken PAM password authentication
+ echo $(PAMLDFLAGS) >> LDFLAGS
+ $(LN) ckp_pmb.c osdepckp.c
+
+ckpstd: # Port standard
+ @echo Standard password authentication
+ $(LN) ckp_$(CHECKPW).c osdepckp.c
+
+ckptwo: # Something plus standard
+ @echo $(CHECKPWALT) password authentication first, then standard
+ $(CAT) ckp_1st.c ckp_$(CHECKPWALT).c ckp_2nd.c ckp_$(CHECKPW).c \
+ ckp_3rd.c > osdepckp.c
+
+
+# SSL support
+
+sslnone:# No SSL
+ @echo Building without SSL support
+ $(LN) ssl_none.c osdepssl.c
+
+sslnopwd: sslunix snopwd
+
+sslunix.nopwd: sslnopwd
+
+sslsco.nopwd: sslsco snopwd
+
+sslunix: sbasic sldunix
+
+sslsco: sbasic sldsco
+
+sbasic: # UNIX OpenSSL
+ @echo Building with SSL
+ $(LN) ssl_unix.c osdepssl.c
+ echo $(SSLCFLAGS) >> OSCFLAGS
+ echo " ssl_onceonlyinit ();" >> linkage.c
+
+snopwd: # Plaintext disable
+ @echo Building with SSL and plaintext passwords disabled unless SSL/TLS
+ echo " mail_parameters (NIL,SET_DISABLEPLAINTEXT,(void *) 2);" >> linkage.c
+
+sldunix:# Normal UNIX SSL load flags
+ echo $(SSLLDFLAGS) >> LDFLAGS
+
+
+sldsco: # SCO SSL load flags
+# Note: Tim Rice says that SSL has to be lunk before other libraries on SCO.
+ echo $(SSLLDFLAGS) `cat LDFLAGS` > LDFLAGS.tmp
+ mv LDFLAGS.tmp LDFLAGS
+
+
+# A monument to a hack of long ago and far away...
+
+love:
+ @echo not war?
diff --git a/imap/src/osdep/unix/Makefile.gss b/imap/src/osdep/unix/Makefile.gss
new file mode 100644
index 00000000..440647e9
--- /dev/null
+++ b/imap/src/osdep/unix/Makefile.gss
@@ -0,0 +1,39 @@
+# ========================================================================
+# Copyright 1988-2007 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# ========================================================================
+
+# Program: GSSAPI makefile
+#
+# Author: Mark Crispin
+# Networks and Distributed Computing
+# Computing & Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, WA 98195
+# Internet: MRC@CAC.Washington.EDU
+#
+# Date: 11 May 1989
+# Last Edited: 4 April 2007
+
+
+# Extended flags needed for additional authenticators. You may need to modify.
+
+GSSDIR=/usr/local
+GSSINCLUDE=$(GSSDIR)/include
+GSSLIB=$(GSSDIR)/lib
+GSSCFLAGS= -I$(GSSINCLUDE) -DGSS_C_NT_HOSTBASED_SERVICE=gss_nt_service_name -DKRB5_DEPRECATED=1
+GSSOLDLDFLAGS= -L$(GSSLIB) -lgssapi_krb5 -lkrb5 -lcrypto -lcom_err
+GSSNEWLDFLAGS= -L$(GSSLIB) -lgssapi_krb5 -lkrb5 -lk5crypto -lcom_err
+
+gss: # GSSAPI Kerberos V flags
+ echo $(GSSCFLAGS) >> OSCFLAGS
+ sh -c '(test -f $(GSSLIB)/libk5crypto.a) && echo $(GSSNEWLDFLAGS) || echo $(GSSOLDLDFLAGS)' >> LDFLAGS
+ echo "#include \"kerb_mit.c\"" >> auths.c
diff --git a/imap/src/osdep/unix/ckp_1st.c b/imap/src/osdep/unix/ckp_1st.c
new file mode 100644
index 00000000..d7769b9d
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_1st.c
@@ -0,0 +1,53 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dual check password part 1
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+struct passwd *checkpw_alt(struct passwd *pw,char *pass,int argc,char *argv[]);
+struct passwd *checkpw_std(struct passwd *pw,char *pass,int argc,char *argv[]);
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ struct passwd *ret;
+ /* in case first checker smashes it */
+ char *user = cpystr (pw->pw_name);
+ if (!(ret = checkpw_alt (pw,pass,argc,argv)))
+ ret = checkpw_std (getpwnam (user),pass,argc,argv);
+ fs_give ((void **) &user);
+ return ret;
+}
+
+/* Redefine alt checker's routine name */
+
+#define checkpw checkpw_alt
diff --git a/imap/src/osdep/unix/ckp_2nd.c b/imap/src/osdep/unix/ckp_2nd.c
new file mode 100644
index 00000000..5207b5d6
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_2nd.c
@@ -0,0 +1,36 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dual check password part 2
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Undefine routine name */
+
+#undef checkpw
+
+/* Redefine std checker's routine name */
+
+#define checkpw checkpw_std
diff --git a/imap/src/osdep/unix/ckp_3rd.c b/imap/src/osdep/unix/ckp_3rd.c
new file mode 100644
index 00000000..c441771a
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_3rd.c
@@ -0,0 +1,32 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dual check password part 3
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Undefine routine name */
+
+#undef checkpw
diff --git a/imap/src/osdep/unix/ckp_a41.c b/imap/src/osdep/unix/ckp_a41.c
new file mode 100644
index 00000000..3018c24c
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_a41.c
@@ -0,0 +1,52 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: AIX 4.1 check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include <login.h>
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ int reenter = 0;
+ char *msg = NIL;
+ char *user = cpystr (pw->pw_name);
+ /* validate password */
+ struct passwd *ret = (pw && !loginrestrictions (user,S_RLOGIN,NIL,&msg) &&
+ !authenticate (user,pass,&reenter,&msg)) ?
+ getpwnam (user) : NIL;
+ /* clean up any message returned */
+ if (msg) fs_give ((void **) &msg);
+ if (user) fs_give ((void **) &user);
+ return ret;
+}
diff --git a/imap/src/osdep/unix/ckp_afs.c b/imap/src/osdep/unix/ckp_afs.c
new file mode 100644
index 00000000..2f3fa6ba
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_afs.c
@@ -0,0 +1,71 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: AFS check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* AFS cleanup
+ * Accepts: data
+ */
+
+void checkpw_cleanup (void *data)
+{
+ ktc_ForgetAllTokens ();
+}
+
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+#undef INIT
+#define min AFS_MIN
+#define max AFS_MAX
+#include <afs/param.h>
+#include <afs/kautils.h>
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ char *reason;
+ /* faster validation for POP servers */
+ if (!strcmp ((char *) mail_parameters (NIL,GET_SERVICENAME,NIL),"pop")) {
+ struct ktc_encryptionKey key;
+ struct ktc_token token;
+ /* just check the password */
+ ka_StringToKey (pass,NIL,&key);
+ if (ka_GetAdminToken (pw->pw_name,"","",&key,600,&token,1)) return NIL;
+ }
+ /* check password and get AFS token */
+ else if (ka_UserAuthenticateGeneral
+ (KA_USERAUTH_VERSION + KA_USERAUTH_DOSETPAG,pw->pw_name,NIL,NIL,
+ pass,0,0,0,&reason)) return NIL;
+ /* arm hook to delete credentials */
+ mail_parameters (NIL,SET_LOGOUTHOOK,(void *) checkpw_cleanup);
+ return pw;
+}
diff --git a/imap/src/osdep/unix/ckp_bsi.c b/imap/src/osdep/unix/ckp_bsi.c
new file mode 100644
index 00000000..128f23ab
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_bsi.c
@@ -0,0 +1,50 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: BSI check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+#include <login_cap.h>
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ char *s,tmp[MAILTMPLEN];
+ login_cap_t *lc;
+ /* make service name */
+ sprintf (tmp,"auth-%s",(char *) mail_parameters (NIL,GET_SERVICENAME,NIL));
+ /* validate password */
+ return ((lc = login_getclass (pw->pw_class)) &&
+ (s = login_getstyle (lc,NIL,tmp)) &&
+ (auth_response (pw->pw_name,lc->lc_class,s,"response",NIL,"",pass)
+ > 0)) ? pw : NIL;
+}
diff --git a/imap/src/osdep/unix/ckp_cyg.c b/imap/src/osdep/unix/ckp_cyg.c
new file mode 100644
index 00000000..28e07394
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_cyg.c
@@ -0,0 +1,64 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Cygwin check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* This module is against my better judgement. If you want to run an imapd
+ * or ipop[23]d you should use the native NT or W2K ports intead of this
+ * Cygwin port. There is no surety that this module works right or will work
+ * right in the future.
+ */
+
+#undef ERROR
+#define NOCRYPT
+#include <windows.h>
+#include <sys/cygwin.h>
+
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+static char *cyg_user = NIL;
+static HANDLE cyg_hdl = NIL;
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ /* flush last pw-checked user */
+ if (cyg_user) fs_give ((void **) &cyg_user);
+ /* forbid if UID 0 or SYSTEM uid */
+ if (!pw->pw_uid || (pw->pw_uid == SYSTEMUID) ||
+ ((cyg_hdl = cygwin_logon_user (pw,pass)) == INVALID_HANDLE_VALUE))
+ return NIL; /* bad UID or password */
+ /* remember user for this handle */
+ cyg_user = cpystr (pw->pw_name);
+ return pw;
+}
+
diff --git a/imap/src/osdep/unix/ckp_dce.c b/imap/src/osdep/unix/ckp_dce.c
new file mode 100644
index 00000000..2a3aed0e
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_dce.c
@@ -0,0 +1,83 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: DCE check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+#include <dce/rpc.h>
+#include <dce/sec_login.h>
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ sec_passwd_rec_t pwr;
+ sec_login_handle_t lhdl;
+ boolean32 rstpwd;
+ sec_login_auth_src_t asrc;
+ error_status_t status;
+ FILE *fd;
+ /* easy case */
+ if (pw->pw_passwd && pw->pw_passwd[0] && pw->pw_passwd[1] &&
+ !strcmp (pw->pw_passwd,(char *) crypt (pass,pw->pw_passwd))) return pw;
+ /* try DCE password cache file */
+ if (fd = fopen (PASSWD_OVERRIDE,"r")) {
+ char *usr = cpystr (pw->pw_name);
+ while ((pw = fgetpwent (fd)) && strcmp (usr,pw->pw_name));
+ fclose (fd); /* finished with cache file */
+ /* validate cached password */
+ if (pw && pw->pw_passwd && pw->pw_passwd[0] && pw->pw_passwd[1] &&
+ !strcmp (pw->pw_passwd,(char *) crypt (pass,pw->pw_passwd))) {
+ fs_give ((void **) &usr);
+ return pw;
+ }
+ if (!pw) pw = getpwnam (usr);
+ fs_give ((void **) &usr);
+ }
+ if (pw) { /* try S-L-O-W DCE... */
+ sec_login_setup_identity ((unsigned_char_p_t) pw->pw_name,
+ sec_login_no_flags,&lhdl,&status);
+ if (status == error_status_ok) {
+ pwr.key.tagged_union.plain = (idl_char *) pass;
+ pwr.key.key_type = sec_passwd_plain;
+ pwr.pepper = NIL;
+ pwr.version_number = sec_passwd_c_version_none;
+ /* validate password with login context */
+ sec_login_validate_identity (lhdl,&pwr,&rstpwd,&asrc,&status);
+ if (!rstpwd && (asrc == sec_login_auth_src_network) &&
+ (status == error_status_ok)) {
+ sec_login_purge_context (&lhdl,&status);
+ if (status == error_status_ok) return pw;
+ }
+ }
+ }
+ return NIL; /* password validation failed */
+}
diff --git a/imap/src/osdep/unix/ckp_gss.c b/imap/src/osdep/unix/ckp_gss.c
new file mode 100644
index 00000000..0ef9388b
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_gss.c
@@ -0,0 +1,90 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Kerberos 5 check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 11 October 2007
+ */
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ char svrnam[MAILTMPLEN],cltnam[MAILTMPLEN];
+ krb5_context ctx;
+ krb5_timestamp now;
+ krb5_principal service;
+ krb5_ccache ccache;
+ krb5_error_code code;
+ krb5_creds *crd = (krb5_creds *) memset (fs_get (sizeof (krb5_creds)),0,
+ sizeof (krb5_creds));
+ struct passwd *ret = NIL;
+ if (*pass) { /* only if password non-empty */
+ /* make service name */
+ sprintf (svrnam,"%.80s@%.512s",
+ (char *) mail_parameters (NIL,GET_SERVICENAME,NIL),
+ tcp_serverhost ());
+ /* make client name with principal */
+ sprintf (cltnam,"%.80s/%.80s",pw->pw_name,
+ (char *) mail_parameters (NIL,GET_SERVICENAME,NIL));
+ /* get a context */
+ if (!krb5_init_context (&ctx)) {
+ /* get time, client and server principals */
+ if (!krb5_timeofday (ctx,&now) &&
+ /* Normally, kerb_cp_svr_name (defined/set in env_unix.c) is NIL, so
+ * only the user name is used as a client principal. A few sites want
+ * to have separate client principals for different services, but many
+ * other sites vehemently object...
+ */
+ !krb5_parse_name (ctx,kerb_cp_svr_name ? cltnam : pw->pw_name,
+ &crd->client) &&
+ !krb5_parse_name (ctx,svrnam,&service) &&
+ !krb5_build_principal_ext(ctx,&crd->server,
+ krb5_princ_realm (ctx,crd->client)->length,
+ krb5_princ_realm (ctx,crd->client)->data,
+ KRB5_TGS_NAME_SIZE,KRB5_TGS_NAME,
+ krb5_princ_realm (ctx,crd->client)->length,
+ krb5_princ_realm (ctx,crd->client)->data,
+ 0)) {
+ /* expire in 3 minutes */
+ crd->times.endtime = now + (3 * 60);
+ if (krb5_cc_resolve (ctx,"MEMORY:pwk",&ccache) ||
+ krb5_cc_initialize (ctx,ccache,crd->client)) ccache = 0;
+ if (!krb5_get_in_tkt_with_password (ctx,NIL,NIL,NIL,NIL,pass,ccache,
+ crd,0) &&
+ !krb5_verify_init_creds (ctx,crd,service,0,ccache ? &ccache : 0,0))
+ ret = pw;
+ krb5_free_creds (ctx,crd);/* flush creds and service principal */
+ krb5_free_principal (ctx,service);
+ }
+ krb5_free_context (ctx); /* don't need context any more */
+ }
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/unix/ckp_nul.c b/imap/src/osdep/unix/ckp_nul.c
new file mode 100644
index 00000000..153ee0ed
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_nul.c
@@ -0,0 +1,40 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Null check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 23 July 1998
+ * Last Edited: 30 August 2006
+ */
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ return NIL; /* always fails */
+}
diff --git a/imap/src/osdep/unix/ckp_os4.c b/imap/src/osdep/unix/ckp_os4.c
new file mode 100644
index 00000000..765bb146
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_os4.c
@@ -0,0 +1,78 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: OSF/1 (Digital UNIX) 4 check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Dummy collection routine
+ * Accepts: how long to wait for user
+ * how to run parameter collection
+ * title
+ * number of prompts
+ * prompts
+ * Returns: collection status
+ *
+ * Because Spider Boardman, who wrote SIA, says that it's needed for buggy SIA
+ * mechanisms, that's why.
+ */
+
+static int checkpw_collect (int timeout,int rendition,uchar_t *title,
+ int nprompts,prompt_t *prompts)
+{
+ switch (rendition) {
+ case SIAONELINER: case SIAINFO: case SIAWARNING: return SIACOLSUCCESS;
+ }
+ return SIACOLABORT; /* another else is bogus */
+}
+
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ int i;
+ char *s;
+ char *name = cpystr (pw->pw_name);
+ char *host = cpystr (tcp_clienthost ());
+ struct passwd *ret = NIL;
+ /* tie off address */
+ if (s = strchr (host,' ')) *s = '\0';
+ if (*host == '[') { /* convert [a.b.c.d] to a.b.c.d */
+ memmove (host,host+1,i = strlen (host + 2));
+ host[i] = '\0';
+ }
+ /* validate password */
+ if (sia_validate_user (checkpw_collect,argc,argv,host,name,NIL,NIL,NIL,pass)
+ == SIASUCCESS) ret = getpwnam (name);
+ fs_give ((void **) &name);
+ fs_give ((void **) &host);
+ return ret;
+}
diff --git a/imap/src/osdep/unix/ckp_pam.c b/imap/src/osdep/unix/ckp_pam.c
new file mode 100644
index 00000000..60c6c1f7
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_pam.c
@@ -0,0 +1,136 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Pluggable Authentication Modules login services
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 31 August 2006
+ */
+
+
+#ifdef MAC_OSX_KLUDGE /* why can't Apple be compatible? */
+#include <pam/pam_appl.h>
+#else
+#include <security/pam_appl.h>
+#endif
+
+struct checkpw_cred {
+ char *uname; /* user name */
+ char *pass; /* password */
+};
+
+/* PAM conversation function
+ * Accepts: number of messages
+ * vector of messages
+ * pointer to response return
+ * application data
+ * Returns: PAM_SUCCESS if OK, response vector filled in, else PAM_CONV_ERR
+ */
+
+static int checkpw_conv (int num_msg,const struct pam_message **msg,
+ struct pam_response **resp,void *appdata_ptr)
+{
+ int i;
+ struct checkpw_cred *cred = (struct checkpw_cred *) appdata_ptr;
+ struct pam_response *reply = fs_get (sizeof (struct pam_response) * num_msg);
+ for (i = 0; i < num_msg; i++) switch (msg[i]->msg_style) {
+ case PAM_PROMPT_ECHO_ON: /* assume want user name */
+ reply[i].resp_retcode = PAM_SUCCESS;
+ reply[i].resp = cpystr (cred->uname);
+ break;
+ case PAM_PROMPT_ECHO_OFF: /* assume want password */
+ reply[i].resp_retcode = PAM_SUCCESS;
+ reply[i].resp = cpystr (cred->pass);
+ break;
+ case PAM_TEXT_INFO:
+ case PAM_ERROR_MSG:
+ reply[i].resp_retcode = PAM_SUCCESS;
+ reply[i].resp = NULL;
+ break;
+ default: /* unknown message style */
+ fs_give ((void **) &reply);
+ return PAM_CONV_ERR;
+ }
+ *resp = reply;
+ return PAM_SUCCESS;
+}
+
+
+/* PAM cleanup
+ * Accepts: handle
+ */
+
+static void checkpw_cleanup (pam_handle_t *hdl)
+{
+#if 0 /* see checkpw() for why this is #if 0 */
+ pam_close_session (hdl,NIL); /* close session [uw]tmp */
+#endif
+ pam_setcred (hdl,PAM_DELETE_CRED);
+ pam_end (hdl,PAM_SUCCESS);
+}
+
+/* Server log in
+ * Accepts: user name string
+ * password string
+ * Returns: T if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ pam_handle_t *hdl;
+ struct pam_conv conv;
+ struct checkpw_cred cred;
+ char *name = cpystr (pw->pw_name);
+ conv.conv = &checkpw_conv;
+ conv.appdata_ptr = &cred;
+ cred.uname = name;
+ cred.pass = pass;
+ if (pw = ((pam_start ((char *) mail_parameters (NIL,GET_SERVICENAME,NIL),
+ pw->pw_name,&conv,&hdl) == PAM_SUCCESS) &&
+ (pam_set_item (hdl,PAM_RHOST,tcp_clientaddr ()) == PAM_SUCCESS) &&
+ (pam_authenticate (hdl,NIL) == PAM_SUCCESS) &&
+ (pam_acct_mgmt (hdl,NIL) == PAM_SUCCESS) &&
+ (pam_setcred (hdl,PAM_ESTABLISH_CRED) == PAM_SUCCESS)) ?
+ getpwnam (name) : NIL) {
+#if 0
+ /*
+ * Some people have reported that this causes a SEGV in strncpy() from
+ * pam_unix.so.1
+ */
+ /*
+ * This pam_open_session() call is inconsistant with how we handle other
+ * platforms, where we don't write [uw]tmp records. However, unlike our
+ * code on other platforms, pam_acct_mgmt() will check those records for
+ * inactivity and deny the authentication.
+ */
+ pam_open_session (hdl,NIL); /* make sure account doesn't go inactive */
+#endif
+ /* arm hook to delete credentials */
+ mail_parameters (NIL,SET_LOGOUTHOOK,(void *) checkpw_cleanup);
+ mail_parameters (NIL,SET_LOGOUTDATA,(void *) hdl);
+ }
+ else checkpw_cleanup (hdl); /* clean up */
+ fs_give ((void **) &name);
+ /* reset log facility in case PAM broke it */
+ if (myServerName) openlog (myServerName,LOG_PID,syslog_facility);
+ return pw;
+}
diff --git a/imap/src/osdep/unix/ckp_pmb.c b/imap/src/osdep/unix/ckp_pmb.c
new file mode 100644
index 00000000..c8a935cd
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_pmb.c
@@ -0,0 +1,128 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Pluggable Authentication Modules login services, buggy systems
+ * (use this instead of ckp_pam.c on Solaris)
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 31 August 2006
+ */
+
+
+#include <security/pam_appl.h>
+
+static char *pam_uname; /* user name */
+static char *pam_pass; /* password */
+
+/* PAM conversation function
+ * Accepts: number of messages
+ * vector of messages
+ * pointer to response return
+ * application data
+ * Returns: PAM_SUCCESS if OK, response vector filled in, else PAM_CONV_ERR
+ */
+
+static int checkpw_conv (int num_msg,const struct pam_message **msg,
+ struct pam_response **resp,void *appdata_ptr)
+{
+ int i;
+ struct pam_response *reply = fs_get (sizeof (struct pam_response) * num_msg);
+ for (i = 0; i < num_msg; i++) switch (msg[i]->msg_style) {
+ case PAM_PROMPT_ECHO_ON: /* assume want user name */
+ reply[i].resp_retcode = PAM_SUCCESS;
+ reply[i].resp = cpystr (pam_uname);
+ break;
+ case PAM_PROMPT_ECHO_OFF: /* assume want password */
+ reply[i].resp_retcode = PAM_SUCCESS;
+ reply[i].resp = cpystr (pam_pass);
+ break;
+ case PAM_TEXT_INFO:
+ case PAM_ERROR_MSG:
+ reply[i].resp_retcode = PAM_SUCCESS;
+ reply[i].resp = NULL;
+ break;
+ default: /* unknown message style */
+ fs_give ((void **) &reply);
+ return PAM_CONV_ERR;
+ }
+ *resp = reply;
+ return PAM_SUCCESS;
+}
+
+
+/* PAM cleanup
+ * Accepts: handle
+ */
+
+static void checkpw_cleanup (pam_handle_t *hdl)
+{
+#if 0 /* see checkpw() for why this is #if 0 */
+ pam_close_session (hdl,NIL); /* close session [uw]tmp */
+#endif
+ pam_setcred (hdl,PAM_DELETE_CRED);
+ pam_end (hdl,PAM_SUCCESS);
+}
+
+/* Server log in
+ * Accepts: user name string
+ * password string
+ * Returns: T if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ pam_handle_t *hdl;
+ struct pam_conv conv;
+ char *name = cpystr (pw->pw_name);
+ conv.conv = &checkpw_conv;
+ pam_uname = pw->pw_name;
+ pam_pass = pass;
+ if (pw = ((pam_start ((char *) mail_parameters (NIL,GET_SERVICENAME,NIL),
+ pw->pw_name,&conv,&hdl) == PAM_SUCCESS) &&
+ (pam_set_item (hdl,PAM_RHOST,tcp_clientaddr ()) == PAM_SUCCESS) &&
+ (pam_authenticate (hdl,NIL) == PAM_SUCCESS) &&
+ (pam_acct_mgmt (hdl,NIL) == PAM_SUCCESS) &&
+ (pam_setcred (hdl,PAM_ESTABLISH_CRED) == PAM_SUCCESS)) ?
+ getpwnam (name) : NIL) {
+#if 0
+ /*
+ * Some people have reported that this causes a SEGV in strncpy() from
+ * pam_unix.so.1
+ */
+ /*
+ * This pam_open_session() call is inconsistant with how we handle other
+ * platforms, where we don't write [uw]tmp records. However, unlike our
+ * code on other platforms, pam_acct_mgmt() will check those records for
+ * inactivity and deny the authentication.
+ */
+ pam_open_session (hdl,NIL); /* make sure account doesn't go inactive */
+#endif
+ /* arm hook to delete credentials */
+ mail_parameters (NIL,SET_LOGOUTHOOK,(void *) checkpw_cleanup);
+ mail_parameters (NIL,SET_LOGOUTDATA,(void *) hdl);
+ }
+ else checkpw_cleanup (hdl); /* clean up */
+ fs_give ((void **) &name);
+ /* reset log facility in case PAM broke it */
+ if (myServerName) openlog (myServerName,LOG_PID,syslog_facility);
+ return pw;
+}
diff --git a/imap/src/osdep/unix/ckp_psx.c b/imap/src/osdep/unix/ckp_psx.c
new file mode 100644
index 00000000..d8ebdaad
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_psx.c
@@ -0,0 +1,101 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: POSIX check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ char tmp[MAILTMPLEN];
+ struct spwd *sp = NIL;
+ time_t left;
+ time_t now = time (0);
+ struct tm *t = gmtime (&now);
+ int zone = t->tm_hour * 60 + t->tm_min;
+ int julian = t->tm_yday;
+ t = localtime (&now); /* get local time now */
+ /* minus UTC minutes since midnight */
+ zone = t->tm_hour * 60 + t->tm_min - zone;
+ /* julian can be one of:
+ * 36x local time is December 31, UTC is January 1, offset -24 hours
+ * 1 local time is 1 day ahead of UTC, offset +24 hours
+ * 0 local time is same day as UTC, no offset
+ * -1 local time is 1 day behind UTC, offset -24 hours
+ * -36x local time is January 1, UTC is December 31, offset +24 hours
+ */
+ if (julian = t->tm_yday -julian)
+ zone += ((julian < 0) == (abs (julian) == 1)) ? -24*60 : 24*60;
+ /* days since 1/1/1970 local time */
+ now = ((now /60) + zone) / (60*24);
+ /* non-shadow authentication */
+ if (!pw->pw_passwd || !pw->pw_passwd[0] || !pw->pw_passwd[1] ||
+ strcmp (pw->pw_passwd,(char *) crypt (pass,pw->pw_passwd))) {
+ /* As far as I've been able to determine, here is how the expiration
+ * fields in the shadow authentication data work:
+ * lstchg last password change date if non-negative. If zero, the
+ * user can not log in without changing password.
+ * max number of days a password is valid if positive
+ * warn number of days of password expiration warning
+ * expire date account expires if positive
+ * inact number of days an accout can be inactive (not checked!)
+ * The expiration day is the *last* day that the password or account
+ * is valid.
+ */
+ /* shadow authentication */
+ if ((sp = getspnam (pw->pw_name)) && sp->sp_lstchg &&
+ ((sp->sp_lstchg < 0) || (sp->sp_max <= 0) ||
+ ((sp->sp_lstchg + sp->sp_max) >= now)) &&
+ ((sp->sp_expire <= 0) || (sp->sp_expire >= now)) &&
+ sp->sp_pwdp && sp->sp_pwdp[0] && sp->sp_pwdp[1] &&
+ !strcmp (sp->sp_pwdp,(char *) crypt (pass,sp->sp_pwdp))) {
+ if ((sp->sp_lstchg > 0) && (sp->sp_max > 0) &&
+ ((left = (sp->sp_lstchg + sp->sp_max) - now) <= sp->sp_warn)) {
+ if (left) {
+ sprintf (tmp,"[ALERT] Password expires in %ld day(s)",(long) left);
+ mm_notify (NIL,tmp,NIL);
+ }
+ else mm_notify (NIL,"[ALERT] Password expires today!",WARN);
+ }
+ if ((sp->sp_expire > 0) && ((left = sp->sp_expire - now) < 28)) {
+ if (left) {
+ sprintf (tmp,"[ALERT] Account expires in %ld day(s)",(long) left);
+ mm_notify (NIL,tmp,NIL);
+ }
+ else mm_notify (NIL,"[ALERT] Account expires today!",WARN);
+ }
+ endspent (); /* don't need shadow password data any more */
+ }
+ else pw = NIL; /* password failed */
+ }
+ return pw;
+}
diff --git a/imap/src/osdep/unix/ckp_sce.c b/imap/src/osdep/unix/ckp_sce.c
new file mode 100644
index 00000000..cff95dda
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_sce.c
@@ -0,0 +1,51 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: SecureWare check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ struct es_passwd *pp;
+ set_auth_parameters (argc,argv);
+ if ((pw->pw_passwd && pw->pw_passwd[0] && pw->pw_passwd[1] &&
+ !strcmp (pw->pw_passwd,(char *) crypt (pass,pw->pw_passwd))) ||
+ ((pp = getespwnam (pw->pw_name)) && !pp->ufld->fd_lock &&
+ !pp->ufld->fd_psw_chg_reqd &&
+ pp->ufld->fd_encrypt && pp->ufld->fd_encrypt[0] &&
+ pp->ufld->fd_encrypt[1] &&
+ !strcmp (pp->ufld->fd_encrypt,bigcrypt (pass,pp->ufld->fd_encrypt))))
+ endprpwent (); /* don't need shadow password data any more */
+ else pw = NIL; /* password failed */
+ return pw;
+}
diff --git a/imap/src/osdep/unix/ckp_sec.c b/imap/src/osdep/unix/ckp_sec.c
new file mode 100644
index 00000000..feddaaf3
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_sec.c
@@ -0,0 +1,50 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: SecureWare check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ struct pr_passwd *pp;
+ set_auth_parameters (argc,argv);
+ if ((pw->pw_passwd && pw->pw_passwd[0] && pw->pw_passwd[1] &&
+ !strcmp (pw->pw_passwd,(char *) crypt (pass,pw->pw_passwd))) ||
+ ((pp = getprpwnam (pw->pw_name)) && !pp->ufld.fd_lock &&
+ pp->ufld.fd_encrypt && pp->ufld.fd_encrypt[0] &&
+ pp->ufld.fd_encrypt[1] &&
+ !strcmp (pp->ufld.fd_encrypt,bigcrypt (pass,pp->ufld.fd_encrypt))))
+ endprpwent (); /* don't need shadow password data any more */
+ else pw = NIL; /* password failed */
+ return pw;
+}
diff --git a/imap/src/osdep/unix/ckp_ssn.c b/imap/src/osdep/unix/ckp_ssn.c
new file mode 100644
index 00000000..edd7d632
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_ssn.c
@@ -0,0 +1,56 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Secure SUN-OS check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include <sys/label.h>
+#include <sys/audit.h>
+#include <pwdadj.h>
+
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ struct passwd_adjunct *pa;
+ char *user = cpystr (pw->pw_name);
+ /* validate user and password */
+ struct passwd *ret =
+ ((pw->pw_passwd && pw->pw_passwd[0] && pw->pw_passwd[1] &&
+ !strcmp (pw->pw_passwd,(char *) crypt (pass,pw->pw_passwd))) ||
+ ((pa = getpwanam (pw->pw_name)) &&
+ pa->pwa_passwd && pa->pwa_passwd[0] && pa->pwa_passwd[1] &&
+ !strcmp (pa->pwa_passwd,(char *) crypt (pass,pa->pwa_passwd)))) ?
+ getpwnam (user) : NIL;
+ if (user) fs_give ((void **) &user);
+ return ret;
+}
diff --git a/imap/src/osdep/unix/ckp_std.c b/imap/src/osdep/unix/ckp_std.c
new file mode 100644
index 00000000..36255cc1
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_std.c
@@ -0,0 +1,42 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Standard check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ return (pw->pw_passwd && pw->pw_passwd[0] && pw->pw_passwd[1] &&
+ !strcmp (pw->pw_passwd,(char *) crypt (pass,pw->pw_passwd))) ?
+ pw : NIL;
+}
diff --git a/imap/src/osdep/unix/ckp_sv4.c b/imap/src/osdep/unix/ckp_sv4.c
new file mode 100644
index 00000000..e4b8ab3b
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_sv4.c
@@ -0,0 +1,90 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: SVR4 check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ char tmp[MAILTMPLEN];
+ struct spwd *sp = NIL;
+ time_t left;
+ time_t now = time (0);
+ struct tm *t = gmtime (&now);
+ int zone = t->tm_hour * 60 + t->tm_min;
+ int julian = t->tm_yday;
+ t = localtime (&now); /* get local time now */
+ /* minus UTC minutes since midnight */
+ zone = t->tm_hour * 60 + t->tm_min - zone;
+ /* julian can be one of:
+ * 36x local time is December 31, UTC is January 1, offset -24 hours
+ * 1 local time is 1 day ahead of UTC, offset +24 hours
+ * 0 local time is same day as UTC, no offset
+ * -1 local time is 1 day behind UTC, offset -24 hours
+ * -36x local time is January 1, UTC is December 31, offset +24 hours
+ */
+ if (julian = t->tm_yday -julian)
+ zone += ((julian < 0) == (abs (julian) == 1)) ? -24*60 : 24*60;
+ /* days since 1/1/1970 local time */
+ now = ((now /60) + zone) / (60*24);
+ /* non-shadow authentication */
+ if (!pw->pw_passwd || !pw->pw_passwd[0] || !pw->pw_passwd[1] ||
+ strcmp (pw->pw_passwd,(char *) crypt (pass,pw->pw_passwd))) {
+ /* As far as I've been able to determine, here is how the expiration
+ * fields in the shadow authentication data work:
+ * lstchg last password change date if non-negative. If zero, the
+ * user can not log in without changing password.
+ * max number of days a password is valid if positive
+ * warn number of days of password expiration warning
+ * The expiration day is the *last* day that the password is valid.
+ */
+ /* shadow authentication */
+ if ((sp = getspnam (pw->pw_name)) && sp->sp_lstchg &&
+ ((sp->sp_lstchg < 0) || (sp->sp_max <= 0) ||
+ ((sp->sp_lstchg + sp->sp_max) >= now)) &&
+ sp->sp_pwdp && sp->sp_pwdp[0] && sp->sp_pwdp[1] &&
+ !strcmp (sp->sp_pwdp,(char *) crypt (pass,sp->sp_pwdp))) {
+ if ((sp->sp_lstchg > 0) && (sp->sp_max > 0) &&
+ ((left = (sp->sp_lstchg + sp->sp_max) - now) <= sp->sp_warn)) {
+ if (left) {
+ sprintf (tmp,"[ALERT] Password expires in %ld day(s)",(long) left);
+ mm_notify (NIL,tmp,NIL);
+ }
+ else mm_notify (NIL,"[ALERT] Password expires today!",WARN);
+ }
+ endspent (); /* don't need shadow password data any more */
+ }
+ else pw = NIL; /* password failed */
+ }
+ return pw;
+}
diff --git a/imap/src/osdep/unix/ckp_svo.c b/imap/src/osdep/unix/ckp_svo.c
new file mode 100644
index 00000000..ffb19a64
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_svo.c
@@ -0,0 +1,89 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Old SVR4 check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ char tmp[MAILTMPLEN];
+ struct spwd *sp = NIL;
+ time_t left;
+ time_t now = time (0);
+ struct tm *t = gmtime (&now);
+ int zone = t->tm_hour * 60 + t->tm_min;
+ int julian = t->tm_yday;
+ t = localtime (&now); /* get local time now */
+ /* minus UTC minutes since midnight */
+ zone = t->tm_hour * 60 + t->tm_min - zone;
+ /* julian can be one of:
+ * 36x local time is December 31, UTC is January 1, offset -24 hours
+ * 1 local time is 1 day ahead of UTC, offset +24 hours
+ * 0 local time is same day as UTC, no offset
+ * -1 local time is 1 day behind UTC, offset -24 hours
+ * -36x local time is January 1, UTC is December 31, offset +24 hours
+ */
+ if (julian = t->tm_yday -julian)
+ zone += ((julian < 0) == (abs (julian) == 1)) ? -24*60 : 24*60;
+ /* days since 1/1/1970 local time */
+ now = ((now /60) + zone) / (60*24);
+ /* non-shadow authentication */
+ if (!pw->pw_passwd || !pw->pw_passwd[0] || !pw->pw_passwd[1] ||
+ strcmp (pw->pw_passwd,(char *) crypt (pass,pw->pw_passwd))) {
+ /* As far as I've been able to determine, here is how the expiration
+ * fields in the shadow authentication data work:
+ * lstchg last password change date if non-negative. If zero, the
+ * user can not log in without changing password.
+ * max number of days a password is valid if positive
+ * The expiration day is the *last* day that the password is valid.
+ */
+ /* shadow authentication */
+ if ((sp = getspnam (pw->pw_name)) && sp->sp_lstchg &&
+ ((sp->sp_lstchg < 0) || (sp->sp_max <= 0) ||
+ ((sp->sp_lstchg + sp->sp_max) >= now)) &&
+ sp->sp_pwdp && sp->sp_pwdp[0] && sp->sp_pwdp[1] &&
+ !strcmp (sp->sp_pwdp,(char *) crypt (pass,sp->sp_pwdp))) {
+ if ((sp->sp_lstchg > 0) && (sp->sp_max > 0) &&
+ ((left = (sp->sp_lstchg + sp->sp_max) - now) <= 28)) {
+ if (left) {
+ sprintf (tmp,"[ALERT] Password expires in %ld day(s)",(long) left);
+ mm_notify (NIL,tmp,NIL);
+ }
+ else mm_notify (NIL,"[ALERT] Password expires today!",WARN);
+ }
+ endspent (); /* don't need shadow password data any more */
+ }
+ else pw = NIL; /* password failed */
+ }
+ return pw;
+}
diff --git a/imap/src/osdep/unix/ckp_ult.c b/imap/src/osdep/unix/ckp_ult.c
new file mode 100644
index 00000000..94e3b0b6
--- /dev/null
+++ b/imap/src/osdep/unix/ckp_ult.c
@@ -0,0 +1,40 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: ULTRIX check password
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Check password
+ * Accepts: login passwd struct
+ * password string
+ * argument count
+ * argument vector
+ * Returns: passwd struct if password validated, NIL otherwise
+ */
+
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
+{
+ return (authenticate_user (pw,pass,NIL) >= 0) ? pw : NIL;
+}
diff --git a/imap/src/osdep/unix/crx_nfs.c b/imap/src/osdep/unix/crx_nfs.c
new file mode 100644
index 00000000..7e60e635
--- /dev/null
+++ b/imap/src/osdep/unix/crx_nfs.c
@@ -0,0 +1,79 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Exclusive create of a file, NFS kludge version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 17 December 1999
+ * Last Edited: 30 August 2006
+ */
+
+
+/* SUN-OS had an NFS, As kludgy as an albatross;
+ * And everywhere that it was installed, It was a total loss.
+ * -- MRC 9/25/91
+ */
+
+/* Exclusive create of a file
+ * Accepts: file name
+ * Return: T if success, NIL if failed, -1 if retry
+ */
+
+long crexcl (char *name)
+{
+ long ret = -1;
+ int i;
+ char hitch[MAILTMPLEN];
+ struct stat sb;
+ int mask = umask (0);
+ /* build hitching post file name */
+ sprintf (hitch,"%s.%lu.%d.",name,(unsigned long) time (0),getpid ());
+ i = strlen (hitch); /* append local host name */
+ gethostname (hitch + i,(MAILTMPLEN - i) - 1);
+ /* try to get hitching-post file */
+ if ((i = open (hitch,O_WRONLY|O_CREAT|O_EXCL,(int) shlock_mode)) >= 0) {
+ close (i); /* close the hitching-post */
+ /* Note: link() may return an error even if it actually succeeded. So we
+ * always check for success via the link count, and ignore the error if
+ * the link count is right.
+ */
+ /* tie hitching-post to lock */
+ i = link (hitch,name) ? errno : 0;
+ /* success if link count now 2 */
+ if (!stat (hitch,&sb) && (sb.st_nlink == 2)) ret = LONGT;
+ else if (i == EPERM) { /* links not allowed? */
+ /* Probably a FAT filesystem on Linux. It can't be NFS, so try creating
+ * the lock file directly.
+ */
+ if ((i = open (name,O_WRONLY|O_CREAT|O_EXCL,(int) shlock_mode)) >= 0){
+ close (i); /* close the file */
+ ret = LONGT; /* success */
+ }
+ /* fail unless error is EEXIST */
+ else if (errno != EEXIST) ret = NIL;
+ }
+ unlink (hitch); /* flush hitching post */
+ }
+ /* fail unless error is EEXIST */
+ else if (errno != EEXIST) ret = NIL;
+ umask (mask); /* restore previous mask */
+ return ret;
+}
diff --git a/imap/src/osdep/unix/crx_std.c b/imap/src/osdep/unix/crx_std.c
new file mode 100644
index 00000000..633137b8
--- /dev/null
+++ b/imap/src/osdep/unix/crx_std.c
@@ -0,0 +1,45 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Exclusive create of a file
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 17 December 1999
+ * Last Edited: 30 August 2006
+ */
+
+/* Exclusive create of a file
+ * Accepts: file name
+ * Return: T if success, NIL if failed, -1 if retry
+ */
+
+long crexcl (char *name)
+{
+ int i;
+ int mask = umask (0);
+ long ret = LONGT;
+ /* try to get the lock */
+ if ((i = open (name,O_WRONLY|O_CREAT|O_EXCL,(int) shlock_mode)) < 0)
+ ret = (errno == EEXIST) ? -1 : NIL;
+ else close (i); /* made the file, now close it */
+ umask (mask); /* restore previous mask */
+ return LONGT; /* success */
+}
diff --git a/imap/src/osdep/unix/drivers b/imap/src/osdep/unix/drivers
new file mode 100755
index 00000000..bd49365f
--- /dev/null
+++ b/imap/src/osdep/unix/drivers
@@ -0,0 +1,36 @@
+#!/bin/sh
+# ========================================================================
+# Copyright 1988-2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# ========================================================================
+
+# Program: Driver Linkage Generator
+#
+# Author: Mark Crispin
+# Networks and Distributed Computing
+# Computing & Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, WA 98195
+# Internet: MRC@CAC.Washington.EDU
+#
+# Date: 11 October 1989
+# Last Edited: 30 August 2006
+
+
+# Erase old driver linkage
+rm -f linkage.[ch]
+
+# Now define the new list
+for driver
+ do
+ echo "extern DRIVER "$driver"driver;" >> linkage.h
+ echo " mail_link (&"$driver"driver); /* link in the $driver driver */" | cat >> linkage.c
+done
diff --git a/imap/src/osdep/unix/dummy.c b/imap/src/osdep/unix/dummy.c
new file mode 100644
index 00000000..b003a0ba
--- /dev/null
+++ b/imap/src/osdep/unix/dummy.c
@@ -0,0 +1,809 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 9 May 1991
+ * Last Edited: 1 June 2007
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include "dummy.h"
+#include "misc.h"
+
+/* Function prototypes */
+
+DRIVER *dummy_valid (char *name);
+void *dummy_parameters (long function,void *value);
+void dummy_list_work (MAILSTREAM *stream,char *dir,char *pat,char *contents,
+ long level);
+long dummy_listed (MAILSTREAM *stream,char delimiter,char *name,
+ long attributes,char *contents);
+long dummy_subscribe (MAILSTREAM *stream,char *mailbox);
+MAILSTREAM *dummy_open (MAILSTREAM *stream);
+void dummy_close (MAILSTREAM *stream,long options);
+long dummy_ping (MAILSTREAM *stream);
+void dummy_check (MAILSTREAM *stream);
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options);
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+/* Dummy routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER dummydriver = {
+ "dummy", /* driver name */
+ DR_LOCAL|DR_MAIL, /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ dummy_valid, /* mailbox is valid for us */
+ dummy_parameters, /* manipulate parameters */
+ dummy_scan, /* scan mailboxes */
+ dummy_list, /* list mailboxes */
+ dummy_lsub, /* list subscribed mailboxes */
+ dummy_subscribe, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ dummy_delete, /* delete mailbox */
+ dummy_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ dummy_open, /* open mailbox */
+ dummy_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message structure */
+ NIL, /* fetch header */
+ NIL, /* fetch text */
+ NIL, /* fetch message data */
+ NIL, /* unique identifier */
+ NIL, /* message number from UID */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ dummy_ping, /* ping mailbox to see if still alive */
+ dummy_check, /* check for new messages */
+ dummy_expunge, /* expunge deleted messages */
+ dummy_copy, /* copy messages to another mailbox */
+ dummy_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM dummyproto = {&dummydriver};
+
+/* Dummy validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *dummy_valid (char *name)
+{
+ char *s,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* must be valid local mailbox */
+ if (name && *name && (*name != '{') && (s = mailboxfile (tmp,name))) {
+ /* indeterminate clearbox INBOX */
+ if (!*s) return &dummydriver;
+ else if (!stat (s,&sbuf)) switch (sbuf.st_mode & S_IFMT) {
+ case S_IFREG:
+ case S_IFDIR:
+ return &dummydriver;
+ }
+ /* blackbox INBOX does not exist yet */
+ else if (!compare_cstring (name,"INBOX")) return &dummydriver;
+ }
+ return NIL;
+}
+
+
+/* Dummy manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *dummy_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = dummy_file ((char *) value,"INBOX");
+ break;
+ }
+ return ret;
+}
+
+/* Dummy scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ DRIVER *drivers;
+ char *s,test[MAILTMPLEN],file[MAILTMPLEN];
+ long i;
+ if (!pat || !*pat) { /* empty pattern? */
+ if (dummy_canonicalize (test,ref,"*")) {
+ /* tie off name at root */
+ if (s = strchr (test,'/')) *++s = '\0';
+ else test[0] = '\0';
+ dummy_listed (stream,'/',test,LATT_NOSELECT,NIL);
+ }
+ }
+ /* get canonical form of name */
+ else if (dummy_canonicalize (test,ref,pat)) {
+ /* found any wildcards? */
+ if (s = strpbrk (test,"%*")) {
+ /* yes, copy name up to that point */
+ strncpy (file,test,i = s - test);
+ file[i] = '\0'; /* tie off */
+ }
+ else strcpy (file,test); /* use just that name then */
+ if (s = strrchr (file,'/')){/* find directory name */
+ *++s = '\0'; /* found, tie off at that point */
+ s = file;
+ }
+ /* silly case */
+ else if ((file[0] == '~') || (file[0] == '#')) s = file;
+ /* do the work */
+ dummy_list_work (stream,s,test,contents,0);
+ /* always an INBOX */
+ if (pmatch ("INBOX",ucase (test))) {
+ /* done if have a dirfmt INBOX */
+ for (drivers = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL);
+ drivers && !(!(drivers->flags & DR_DISABLE) &&
+ (drivers->flags & DR_DIRFMT) &&
+ (*drivers->valid) ("INBOX")); drivers = drivers->next);
+ /* list INBOX appropriately */
+ dummy_listed (stream,drivers ? '/' : NIL,"INBOX",
+ drivers ? NIL : LATT_NOINFERIORS,contents);
+ }
+ }
+}
+
+
+/* Dummy list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ dummy_scan (stream,ref,pat,NIL);
+}
+
+/* Dummy list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ void *sdb = NIL;
+ char *s,*t,test[MAILTMPLEN],tmp[MAILTMPLEN];
+ int showuppers = pat[strlen (pat) - 1] == '%';
+ /* get canonical form of name */
+ if (dummy_canonicalize (test,ref,pat) && (s = sm_read (&sdb))) do
+ if (*s != '{') {
+ if (!compare_cstring (s,"INBOX") &&
+ pmatch ("INBOX",ucase (strcpy (tmp,test))))
+ mm_lsub (stream,NIL,s,LATT_NOINFERIORS);
+ else if (pmatch_full (s,test,'/')) mm_lsub (stream,'/',s,NIL);
+ else while (showuppers && (t = strrchr (s,'/'))) {
+ *t = '\0'; /* tie off the name */
+ if (pmatch_full (s,test,'/')) mm_lsub (stream,'/',s,LATT_NOSELECT);
+ }
+ }
+ while (s = sm_read (&sdb)); /* until no more subscriptions */
+}
+
+
+/* Dummy subscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to add to subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_subscribe (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* must be valid local mailbox */
+ if ((s = mailboxfile (tmp,mailbox)) && *s && !stat (s,&sbuf))
+ switch (sbuf.st_mode & S_IFMT) {
+ case S_IFDIR: /* allow but snarl */
+ sprintf (tmp,"CLIENT BUG DETECTED: subscribe of non-mailbox directory %.80s",
+ mailbox);
+ MM_NOTIFY (stream,tmp,WARN);
+ case S_IFREG:
+ return sm_subscribe (mailbox);
+ }
+ sprintf (tmp,"Can't subscribe %.80s: not a mailbox",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+}
+
+/* Dummy list mailboxes worker routine
+ * Accepts: mail stream
+ * directory name to search
+ * search pattern
+ * string to scan
+ * search level
+ */
+
+void dummy_list_work (MAILSTREAM *stream,char *dir,char *pat,char *contents,
+ long level)
+{
+ DRIVER *drivers;
+ dirfmttest_t dt;
+ DIR *dp;
+ struct direct *d;
+ struct stat sbuf;
+ char tmp[MAILTMPLEN],path[MAILTMPLEN];
+ size_t len = 0;
+ /* punt if bogus name */
+ if (!mailboxdir (tmp,dir,NIL)) return;
+ if (dp = opendir (tmp)) { /* do nothing if can't open directory */
+ /* see if a non-namespace directory format */
+ for (drivers = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL), dt = NIL;
+ dir && !dt && drivers; drivers = drivers->next)
+ if (!(drivers->flags & DR_DISABLE) && (drivers->flags & DR_DIRFMT) &&
+ (*drivers->valid) (dir))
+ dt = mail_parameters ((*drivers->open) (NIL),GET_DIRFMTTEST,NIL);
+ /* list it if at top-level */
+ if (!level && dir && pmatch_full (dir,pat,'/') && !pmatch (dir,"INBOX"))
+ dummy_listed (stream,'/',dir,dt ? NIL : LATT_NOSELECT,contents);
+
+ /* scan directory, ignore . and .. */
+ if (!dir || dir[(len = strlen (dir)) - 1] == '/') while (d = readdir (dp))
+ if ((!(dt && (*dt) (d->d_name))) &&
+ ((d->d_name[0] != '.') ||
+ (((long) mail_parameters (NIL,GET_HIDEDOTFILES,NIL)) ? NIL :
+ (d->d_name[1] && (((d->d_name[1] != '.') || d->d_name[2]))))) &&
+ ((len + strlen (d->d_name)) <= NETMAXMBX)) {
+ /* see if name is useful */
+ if (dir) sprintf (tmp,"%s%s",dir,d->d_name);
+ else strcpy (tmp,d->d_name);
+ /* make sure useful and can get info */
+ if ((pmatch_full (strcpy (path,tmp),pat,'/') ||
+ pmatch_full (strcat (path,"/"),pat,'/') ||
+ dmatch (path,pat,'/')) &&
+ mailboxdir (path,dir,"x") && (len = strlen (path)) &&
+ strcpy (path+len-1,d->d_name) && !stat (path,&sbuf)) {
+ /* only interested in file type */
+ switch (sbuf.st_mode & S_IFMT) {
+ case S_IFDIR: /* directory? */
+ /* form with trailing / */
+ sprintf (path,"%s/",tmp);
+ /* skip listing if INBOX */
+ if (!pmatch (tmp,"INBOX")) {
+ if (pmatch_full (tmp,pat,'/')) {
+ if (!dummy_listed (stream,'/',tmp,LATT_NOSELECT,contents))
+ break;
+ }
+ /* try again with trailing / */
+ else if (pmatch_full (path,pat,'/') &&
+ !dummy_listed (stream,'/',path,LATT_NOSELECT,contents))
+ break;
+ }
+ if (dmatch (path,pat,'/') &&
+ (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL)))
+ dummy_list_work (stream,path,pat,contents,level+1);
+ break;
+ case S_IFREG: /* ordinary name */
+ /* Must use ctime for systems that don't update mtime properly */
+ if (pmatch_full (tmp,pat,'/') && compare_cstring (tmp,"INBOX"))
+ dummy_listed (stream,'/',tmp,LATT_NOINFERIORS +
+ ((sbuf.st_size && (sbuf.st_atime < sbuf.st_ctime))?
+ LATT_MARKED : LATT_UNMARKED),contents);
+ break;
+ }
+ }
+ }
+ closedir (dp); /* all done, flush directory */
+ }
+}
+
+/* Scan file for contents
+ * Accepts: driver to use
+ * file name
+ * desired contents
+ * length of contents
+ * size of file
+ * Returns: NIL if contents not found, T if found
+ */
+
+long scan_contents (DRIVER *dtb,char *name,char *contents,
+ unsigned long csiz,unsigned long fsiz)
+{
+ scancontents_t sc = dtb ?
+ (scancontents_t) (*dtb->parameters) (GET_SCANCONTENTS,NIL) : NIL;
+ return (*(sc ? sc : dummy_scan_contents)) (name,contents,csiz,fsiz);
+}
+
+
+/* Scan file for contents
+ * Accepts: file name
+ * desired contents
+ * length of contents
+ * size of file
+ * Returns: NIL if contents not found, T if found
+ */
+
+#define BUFSIZE 4*MAILTMPLEN
+
+long dummy_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz)
+{
+ int fd;
+ unsigned long ssiz,bsiz;
+ char *buf;
+ /* forget it if can't select or open */
+ if ((fd = open (name,O_RDONLY,NIL)) >= 0) {
+ /* get buffer including slop */
+ buf = (char *) fs_get (BUFSIZE + (ssiz = 4 * ((csiz / 4) + 1)) + 1);
+ memset (buf,'\0',ssiz); /* no slop area the first time */
+ while (fsiz) { /* until end of file */
+ read (fd,buf+ssiz,bsiz = min (fsiz,BUFSIZE));
+ if (search ((unsigned char *) buf,bsiz+ssiz,
+ (unsigned char *) contents,csiz)) break;
+ memcpy (buf,buf+BUFSIZE,ssiz);
+ fsiz -= bsiz; /* note that we read that much */
+ }
+ fs_give ((void **) &buf); /* flush buffer */
+ close (fd); /* finished with file */
+ if (fsiz) return T; /* found */
+ }
+ return NIL; /* not found */
+}
+
+/* Mailbox found
+ * Accepts: MAIL stream
+ * hierarchy delimiter
+ * mailbox name
+ * attributes
+ * contents to search before calling mm_list()
+ * Returns: NIL if should abort hierarchy search, else T (currently always)
+ */
+
+long dummy_listed (MAILSTREAM *stream,char delimiter,char *name,
+ long attributes,char *contents)
+{
+ DRIVER *d;
+ DIR *dp;
+ struct direct *dr;
+ dirfmttest_t dt;
+ unsigned long csiz;
+ struct stat sbuf;
+ int nochild;
+ char *s,tmp[MAILTMPLEN];
+ if (!(attributes & LATT_NOINFERIORS) && mailboxdir (tmp,name,NIL) &&
+ (dp = opendir (tmp))) { /* if not \NoInferiors */
+ /* locate dirfmttest if any */
+ for (d = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL), dt = NIL;
+ !dt && d; d = d->next)
+ if (!(d->flags & DR_DISABLE) && (d->flags & DR_DIRFMT) &&
+ (*d->valid) (name))
+ dt = mail_parameters ((*d->open) (NIL),GET_DIRFMTTEST,NIL);
+ /* scan directory for children */
+ for (nochild = T; nochild && (dr = readdir (dp)); )
+ if ((!(dt && (*dt) (dr->d_name))) &&
+ ((dr->d_name[0] != '.') ||
+ (((long) mail_parameters (NIL,GET_HIDEDOTFILES,NIL)) ? NIL :
+ (dr->d_name[1] && ((dr->d_name[1] != '.') || dr->d_name[2])))))
+ nochild = NIL;
+ attributes |= nochild ? LATT_HASNOCHILDREN : LATT_HASCHILDREN;
+ closedir (dp); /* all done, flush directory */
+ }
+ d = NIL; /* don't \NoSelect dir if it has a driver */
+ if ((attributes & LATT_NOSELECT) && (d = mail_valid (NIL,name,NIL)) &&
+ (d != &dummydriver)) attributes &= ~LATT_NOSELECT;
+ if (!contents || /* notify main program */
+ (!(attributes & LATT_NOSELECT) && (csiz = strlen (contents)) &&
+ (s = mailboxfile (tmp,name)) &&
+ (*s || (s = mail_parameters (NIL,GET_INBOXPATH,tmp))) &&
+ !stat (s,&sbuf) && (d || (csiz <= sbuf.st_size)) &&
+ SAFE_SCAN_CONTENTS (d,tmp,contents,csiz,sbuf.st_size)))
+ mm_list (stream,delimiter,name,attributes);
+ return T;
+}
+
+/* Dummy create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,tmp[MAILTMPLEN];
+ long ret = NIL;
+ /* validate name */
+ if (!(compare_cstring (mailbox,"INBOX") && (s = dummy_file (tmp,mailbox)))) {
+ sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
+ MM_LOG (tmp,ERROR);
+ }
+ /* create the name, done if made directory */
+ else if ((ret = dummy_create_path (stream,tmp,get_dir_protection(mailbox)))&&
+ (s = strrchr (s,'/')) && !s[1]) return T;
+ return ret ? set_mbx_protections (mailbox,tmp) : NIL;
+}
+
+/* Dummy create path
+ * Accepts: mail stream
+ * path name to create
+ * directory mode
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode)
+{
+ struct stat sbuf;
+ char c,*s,tmp[MAILTMPLEN];
+ int fd;
+ long ret = NIL;
+ char *t = strrchr (path,'/');
+ int wantdir = t && !t[1];
+ int mask = umask (0);
+ if (wantdir) *t = '\0'; /* flush trailing delimiter for directory */
+ if (s = strrchr (path,'/')) { /* found superior to this name? */
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (path,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,path,dirmode)) {
+ umask (mask); /* restore mask */
+ return NIL;
+ }
+ *s = c; /* restore full name */
+ }
+ if (wantdir) { /* want to create directory? */
+ ret = !mkdir (path,(int) dirmode);
+ *t = '/'; /* restore directory delimiter */
+ }
+ /* create file */
+ else if ((fd = open (path,O_WRONLY|O_CREAT|O_EXCL,
+ (long) mail_parameters(NIL,GET_MBXPROTECTION,NIL))) >=0)
+ ret = !close (fd);
+ if (!ret) { /* error? */
+ sprintf (tmp,"Can't create mailbox node %.80s: %.80s",path,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ umask (mask); /* restore mask */
+ return ret; /* return status */
+}
+
+/* Dummy delete mailbox
+ * Accepts: mail stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_delete (MAILSTREAM *stream,char *mailbox)
+{
+ struct stat sbuf;
+ char *s,tmp[MAILTMPLEN];
+ if (!(s = dummy_file (tmp,mailbox))) {
+ sprintf (tmp,"Can't delete - invalid name: %.80s",s);
+ MM_LOG (tmp,ERROR);
+ }
+ /* no trailing / (workaround BSD kernel bug) */
+ if ((s = strrchr (tmp,'/')) && !s[1]) *s = '\0';
+ if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) == S_IFDIR) ?
+ rmdir (tmp) : unlink (tmp)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %.80s",mailbox,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ return T; /* return success */
+}
+
+/* Mail rename mailbox
+ * Accepts: mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ struct stat sbuf;
+ char c,*s,tmp[MAILTMPLEN],mbx[MAILTMPLEN],oldname[MAILTMPLEN];
+ /* no trailing / allowed */
+ if (!dummy_file (oldname,old) || !(s = dummy_file (mbx,newname)) ||
+ stat (oldname,&sbuf) || ((s = strrchr (s,'/')) && !s[1] &&
+ ((sbuf.st_mode & S_IFMT) != S_IFDIR))) {
+ sprintf (mbx,"Can't rename %.80s to %.80s: invalid name",old,newname);
+ MM_LOG (mbx,ERROR);
+ return NIL;
+ }
+ if (s) { /* found a directory delimiter? */
+ if (!s[1]) *s = '\0'; /* ignore trailing delimiter */
+ else { /* found superior to destination name? */
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (mbx,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create (stream,mbx)) return NIL;
+ *s = c; /* restore full name */
+ }
+ }
+ /* rename of non-ex INBOX creates dest */
+ if (!compare_cstring (old,"INBOX") && stat (oldname,&sbuf))
+ return dummy_create (NIL,mbx);
+ if (rename (oldname,mbx)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",old,newname,
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ return T; /* return success */
+}
+
+/* Dummy open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *dummy_open (MAILSTREAM *stream)
+{
+ int fd;
+ char err[MAILTMPLEN],tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* OP_PROTOTYPE call */
+ if (!stream) return &dummyproto;
+ err[0] = '\0'; /* no error message yet */
+ /* can we open the file? */
+ if (!dummy_file (tmp,stream->mailbox))
+ sprintf (err,"Can't open this name: %.80s",stream->mailbox);
+ else if ((fd = open (tmp,O_RDONLY,NIL)) < 0) {
+ /* no, error unless INBOX */
+ if (compare_cstring (stream->mailbox,"INBOX"))
+ sprintf (err,"%.80s: %.80s",strerror (errno),stream->mailbox);
+ }
+ else { /* file had better be empty then */
+ fstat (fd,&sbuf); /* sniff at its size */
+ close (fd);
+ if ((sbuf.st_mode & S_IFMT) != S_IFREG)
+ sprintf (err,"Can't open %.80s: not a selectable mailbox",
+ stream->mailbox);
+ else if (sbuf.st_size) /* bogus format if non-empty */
+ sprintf (err,"Can't open %.80s (file %.80s): not in valid mailbox format",
+ stream->mailbox,tmp);
+ }
+ if (err[0]) { /* if an error happened */
+ MM_LOG (err,stream->silent ? WARN : ERROR);
+ return NIL;
+ }
+ else if (!stream->silent) { /* only if silence not requested */
+ mail_exists (stream,0); /* say there are 0 messages */
+ mail_recent (stream,0); /* and certainly no recent ones! */
+ stream->uid_validity = time (0);
+ }
+ stream->inbox = T; /* note that it's an INBOX */
+ return stream; /* return success */
+}
+
+
+/* Dummy close
+ * Accepts: MAIL stream
+ * options
+ */
+
+void dummy_close (MAILSTREAM *stream,long options)
+{
+ /* return silently */
+}
+
+/* Dummy ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long dummy_ping (MAILSTREAM *stream)
+{
+ MAILSTREAM *test;
+ if (time (0) >= /* time to do another test? */
+ ((time_t) (stream->gensym +
+ (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL)))) {
+ /* has mailbox format changed? */
+ if ((test = mail_open (NIL,stream->mailbox,OP_PROTOTYPE)) &&
+ (test->dtb != stream->dtb) &&
+ (test = mail_open (NIL,stream->mailbox,NIL))) {
+ /* preserve some resources */
+ test->original_mailbox = stream->original_mailbox;
+ stream->original_mailbox = NIL;
+ test->sparep = stream->sparep;
+ stream->sparep = NIL;
+ test->sequence = stream->sequence;
+ mail_close ((MAILSTREAM *) /* flush resources used by dummy stream */
+ memcpy (fs_get (sizeof (MAILSTREAM)),stream,
+ sizeof (MAILSTREAM)));
+ /* swap the streams */
+ memcpy (stream,test,sizeof (MAILSTREAM));
+ fs_give ((void **) &test);/* flush test now that copied */
+ /* make sure application knows */
+ mail_exists (stream,stream->recent = stream->nmsgs);
+ }
+ /* still hasn't changed */
+ else stream->gensym = time (0);
+ }
+ return T;
+}
+
+
+/* Dummy check mailbox
+ * Accepts: MAIL stream
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+void dummy_check (MAILSTREAM *stream)
+{
+ dummy_ping (stream); /* invoke ping */
+}
+
+
+/* Dummy expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ return LONGT;
+}
+
+/* Dummy copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * options
+ * Returns: T if copy successful, else NIL
+ */
+
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) fatal ("Impossible dummy_copy");
+ return NIL;
+}
+
+
+/* Dummy append message string
+ * Accepts: mail stream
+ * destination mailbox
+ * append callback function
+ * data for callback
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd = -1;
+ int e;
+ char tmp[MAILTMPLEN];
+ MAILSTREAM *ts = default_proto (T);
+ /* append to INBOX? */
+ if (!compare_cstring (mailbox,"INBOX")) {
+ /* yes, if no empty proto try creating */
+ if (!ts && !(*(ts = default_proto (NIL))->dtb->create) (ts,"INBOX"))
+ ts = NIL;
+ }
+ else if (dummy_file (tmp,mailbox) && ((fd = open (tmp,O_RDONLY,NIL)) < 0)) {
+ if ((e = errno) == ENOENT) /* failed, was it no such file? */
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ sprintf (tmp,"%.80s: %.80s",strerror (e),mailbox);
+ MM_LOG (tmp,ERROR); /* pass up error */
+ return NIL; /* always fails */
+ }
+ else if (fd >= 0) { /* found file? */
+ fstat (fd,&sbuf); /* get its size */
+ close (fd); /* toss out the fd */
+ if (sbuf.st_size) ts = NIL; /* non-empty file? */
+ }
+ if (ts) return (*ts->dtb->append) (stream,mailbox,af,data);
+ sprintf (tmp,"Indeterminate mailbox format: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+}
+
+/* Dummy mail generate file string
+ * Accepts: temporary buffer to write into
+ * mailbox name string
+ * Returns: local file string or NIL if failure
+ */
+
+char *dummy_file (char *dst,char *name)
+{
+ char *s = mailboxfile (dst,name);
+ /* return our standard inbox */
+ return (s && !*s) ? strcpy (dst,sysinbox ()) : s;
+}
+
+
+/* Dummy canonicalize name
+ * Accepts: buffer to write name
+ * reference
+ * pattern
+ * Returns: T if success, NIL if failure
+ */
+
+long dummy_canonicalize (char *tmp,char *ref,char *pat)
+{
+ unsigned long i;
+ char *s;
+ if (ref) { /* preliminary reference check */
+ if (*ref == '{') return NIL;/* remote reference not allowed */
+ else if (!*ref) ref = NIL; /* treat empty reference as no reference */
+ }
+ switch (*pat) {
+ case '#': /* namespace name */
+ if (mailboxfile (tmp,pat)) strcpy (tmp,pat);
+ else return NIL; /* unknown namespace */
+ break;
+ case '{': /* remote names not allowed */
+ return NIL;
+ case '/': /* rooted name */
+ case '~': /* home directory name */
+ if (!ref || (*ref != '#')) {/* non-namespace reference? */
+ strcpy (tmp,pat); /* yes, ignore */
+ break;
+ }
+ /* fall through */
+ default: /* apply reference for all other names */
+ if (!ref) strcpy (tmp,pat); /* just copy if no namespace */
+ else if ((*ref != '#') || mailboxfile (tmp,ref)) {
+ /* wants root of name? */
+ if (*pat == '/') strcpy (strchr (strcpy (tmp,ref),'/'),pat);
+ /* otherwise just append */
+ else sprintf (tmp,"%s%s",ref,pat);
+ }
+ else return NIL; /* unknown namespace */
+ }
+ /* count wildcards */
+ for (i = 0, s = tmp; *s; *s++) if ((*s == '*') || (*s == '%')) ++i;
+ if (i > MAXWILDCARDS) { /* ridiculous wildcarding? */
+ MM_LOG ("Excessive wildcards in LIST/LSUB",ERROR);
+ return NIL;
+ }
+ return T;
+}
diff --git a/imap/src/osdep/unix/dummy.h b/imap/src/osdep/unix/dummy.h
new file mode 100644
index 00000000..32650e06
--- /dev/null
+++ b/imap/src/osdep/unix/dummy.h
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 9 May 1991
+ * Last Edited: 30 August 2006
+ */
+
+/* Exported function prototypes */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat);
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long scan_contents (DRIVER *dtb,char *name,char *contents,
+ unsigned long csiz,unsigned long fsiz);
+long dummy_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz);
+long dummy_create (MAILSTREAM *stream,char *mailbox);
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode);
+long dummy_delete (MAILSTREAM *stream,char *mailbox);
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname);
+char *dummy_file (char *dst,char *name);
+long dummy_canonicalize (char *tmp,char *ref,char *pat);
diff --git a/imap/src/osdep/unix/env_unix.c b/imap/src/osdep/unix/env_unix.c
new file mode 100644
index 00000000..6b2c4471
--- /dev/null
+++ b/imap/src/osdep/unix/env_unix.c
@@ -0,0 +1,1847 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX environment routines
+ *
+ * Author: Mark Crispin
+ * UW Technology
+ * University of Washington
+ * Seattle, WA 98195
+ * Internet: MRC@Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 23 February 2009
+ */
+
+#include <grp.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+
+/* in case stat.h is ancient */
+
+#ifndef S_IRUSR
+#define S_IRUSR S_IREAD
+#endif
+#ifndef S_IWUSR
+#define S_IWUSR S_IWRITE
+#endif
+#ifndef S_IXUSR
+#define S_IXUSR S_IEXEC
+#endif
+#ifndef S_IRGRP
+#define S_IRGRP (S_IREAD >> 3)
+#endif
+#ifndef S_IWGRP
+#define S_IWGRP (S_IWRITE >> 3)
+#endif
+#ifndef S_IXGRP
+#define S_IXGRP (S_IEXEC >> 3)
+#endif
+#ifndef S_IROTH
+#define S_IROTH (S_IREAD >> 6)
+#endif
+#ifndef S_IWOTH
+#define S_IWOTH (S_IWRITE >> 6)
+#endif
+#ifndef S_IXOTH
+#define S_IXOTH (S_IEXEC >> 6)
+#endif
+
+/* c-client environment parameters */
+
+static char *myUserName = NIL; /* user name */
+static char *myHomeDir = NIL; /* home directory name */
+static char *myServerName = NIL;/* server name */
+static char *myLocalHost = NIL; /* local host name */
+static char *myNewsrc = NIL; /* newsrc file name */
+static char *mailsubdir = NIL; /* mailbox subdirectory name */
+static char *sysInbox = NIL; /* system inbox name */
+static char *newsActive = NIL; /* news active file */
+static char *newsSpool = NIL; /* news spool */
+static char *blackBoxDir = NIL; /* black box directory name */
+ /* black box default home directory */
+static char *blackBoxDefaultHome = NIL;
+static char *sslCApath = NIL; /* non-standard CA path */
+static short anonymous = NIL; /* is anonymous */
+static short blackBox = NIL; /* is a black box */
+static short closedBox = NIL; /* is a closed box (uses chroot() jail) */
+static short restrictBox = NIL; /* is a restricted box */
+static short has_no_life = NIL; /* is a cretin with no life */
+ /* block environment init */
+static short block_env_init = NIL;
+static short hideDotFiles = NIL;/* hide files whose names start with . */
+ /* advertise filesystem root */
+static short advertisetheworld = NIL;
+ /* only advertise own mailboxes and #shared */
+static short limitedadvertise = NIL;
+ /* disable automatic shared namespaces */
+static short noautomaticsharedns = NIL;
+static short no822tztext = NIL; /* disable RFC [2]822 timezone text */
+ /* client principals include service name */
+static short kerb_cp_svr_name = NIL;
+static long locktimeout = 5; /* default lock timeout in minutes */
+ /* default prototypes */
+static MAILSTREAM *createProto = NIL;
+static MAILSTREAM *appendProto = NIL;
+ /* default user flags */
+static char *userFlags[NUSERFLAGS] = {NIL};
+static NAMESPACE *nslist[3]; /* namespace list */
+static int logtry = 3; /* number of server login tries */
+ /* block notification */
+static blocknotify_t mailblocknotify = mm_blocknotify;
+ /* logout function */
+static logouthook_t maillogouthook = NIL;
+ /* logout data */
+static void *maillogoutdata = NIL;
+ /* allow user config files */
+static short allowuserconfig = NIL;
+ /* 1 = disable plaintext, 2 = if not SSL */
+static long disablePlaintext = NIL;
+static long list_max_level = 20;/* maximum level of list recursion */
+ /* facility for syslog */
+static int syslog_facility = LOG_MAIL;
+
+/* Path of the privileged system lock program (mlock). Normally set by
+ * logic test.
+ */
+
+static char *lockpgm = LOCKPGM;
+
+/* Directory used for shared locks. MUST be the same for all users of the
+ * system, and MUST be protected 1777. /var/tmp may be preferable on some
+ * systems.
+ */
+
+static const char *tmpdir = "/tmp";
+
+/* Do not change shlock_mode. Doing so can cause mailbox corruption and
+ * denial of service. It also defeats the entire purpose of the shared
+ * lock mechanism. The right way to avoid shared locks is to set up a
+ * closed box (see the closedBox setting).
+ */
+
+ /* shared lock mode */
+static const int shlock_mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
+
+
+/* It is STRONGLY recommended that you do not change dotlock_mode. Doing so
+ * can cause denial of service with old dot-lock files left lying around.
+ * However, since dot-locks are only used with traditional UNIX and MMDF
+ * formats which are not normally shared, it is much less harmful to tamper
+ * with this than with shlock_mode.
+ */
+
+ /* dot-lock mode */
+static long dotlock_mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
+
+/* File/directory access and protection policies */
+
+/* Unlike shlock_mode, the ????_protection modes are intended to be fully
+ * customizable according to site policy. The values here are recommended
+ * settings, based upon the documented purposes of the namespaces.
+ */
+
+ /* user space - only owner can read/write */
+static char *myMailboxDir = NIL;/* user space directory name */
+ /* default file protection */
+static long mbx_protection = S_IRUSR|S_IWUSR;
+ /* default directory protection */
+static long dir_protection = S_IRUSR|S_IWUSR|S_IXUSR;
+
+ /* user space for user "anonymous" */
+ /* anonymous home directory */
+static char *anonymousHome = NIL;
+
+ /* #ftp - everybody can read, only owner can write */
+static char *ftpHome = NIL; /* ftp export home directory */
+ /* default ftp file protection */
+static long ftp_protection = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
+static long ftp_dir_protection =/* default ftp directory protection */
+ S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
+
+ /* #public - everybody can read/write */
+static char *publicHome = NIL; /* public home directory */
+static long public_protection = /* default public file protection */
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
+ /* default public directory protection */
+static long public_dir_protection =
+ S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH;
+
+ /* #shared/ - owner and group members can read/write */
+static char *sharedHome = NIL; /* shared home directory */
+ /* default shared file protection */
+static long shared_protection = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP;
+ /* default shared directory protection */
+static long shared_dir_protection =
+ S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP;
+
+/* OS bug workarounds - should be avoided at all cost */
+
+
+/* Don't set fcntlhangbug unless you really have to, since it risks mailbox
+ * corruption. The flocksim.c mechanism is designed to detect NFS access
+ * and no-op in that cases only, so this flag should be unnecessary.
+ */
+
+static short fcntlhangbug = NIL;/* flock() emulator using fcntl() is a no-op */
+
+
+/* Don't set netfsstatbug unless you really have to, since it dramatically
+ * slows down traditional UNIX and MMDF mailbox performance.
+ */
+
+static short netfsstatbug = NIL;/* compensate for broken stat() on network
+ * filesystems (AFS and old NFS)
+ */
+
+
+/* Note: setting disableLockWarning means that you assert that the
+ * so-modified copy of this software will NEVER be used:
+ * 1) in conjunction with any software which expects .lock files
+ * 2) to access NFS-mounted files and directories
+ *
+ * Unless both of these conditions apply, then do not set this flag.
+ * Instead, read the FAQ (item 7.10) and either use 1777 protection
+ * on the mail spool, or install mlock.
+ *
+ * In addition, by setting this flag you also agree that you are fully
+ * legally and morally responsible when (not if) mail files are damaged
+ * as the result of your choice.
+ *
+ * The mlock tool exists for a reason. Use it.
+ */
+ /* disable warning if can't make .lock file */
+static short disableLockWarning = NIL;
+
+/* UNIX Namespaces */
+
+ /* personal mh namespace */
+static NAMESPACE nsmhf = {"#mh/",'/',NIL,NIL};
+static NAMESPACE nsmh = {"#mhinbox",NIL,NIL,&nsmhf};
+ /* home namespace */
+static NAMESPACE nshome = {"",'/',NIL,&nsmh};
+ /* UNIX other user namespace */
+static NAMESPACE nsunixother = {"~",'/',NIL,NIL};
+ /* black box other user namespace */
+static NAMESPACE nsblackother = {"/",'/',NIL,NIL};
+ /* public (anonymous OK) namespace */
+static NAMESPACE nspublic = {"#public/",'/',NIL,NIL};
+ /* netnews namespace */
+static NAMESPACE nsnews = {"#news.",'.',NIL,&nspublic};
+ /* FTP export namespace */
+static NAMESPACE nsftp = {"#ftp/",'/',NIL,&nsnews};
+ /* shared (no anonymous) namespace */
+static NAMESPACE nsshared = {"#shared/",'/',NIL,&nsftp};
+ /* world namespace */
+static NAMESPACE nsworld = {"/",'/',NIL,&nsshared};
+ /* only shared and public namespaces */
+static NAMESPACE nslimited = {"#shared/",'/',NIL,&nspublic};
+
+
+
+#include "write.c" /* include safe writing routines */
+#include "crexcl.c" /* include exclusive create */
+#include "pmatch.c" /* include wildcard pattern matcher */
+
+/* Get all authenticators */
+
+#include "auths.c"
+
+/* Environment manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *env_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_NAMESPACE:
+ ret = (void *) nslist;
+ break;
+ case SET_USERNAME:
+ if (myUserName) fs_give ((void **) &myUserName);
+ myUserName = cpystr ((char *) value);
+ case GET_USERNAME:
+ ret = (void *) myUserName;
+ break;
+ case SET_HOMEDIR:
+ if (myHomeDir) fs_give ((void **) &myHomeDir);
+ myHomeDir = cpystr ((char *) value);
+ case GET_HOMEDIR:
+ ret = (void *) myHomeDir;
+ break;
+ case SET_LOCALHOST:
+ if (myLocalHost) fs_give ((void **) &myLocalHost);
+ myLocalHost = cpystr ((char *) value);
+ case GET_LOCALHOST:
+ ret = (void *) myLocalHost;
+ break;
+ case SET_NEWSRC:
+ if (myNewsrc) fs_give ((void **) &myNewsrc);
+ myNewsrc = cpystr ((char *) value);
+ case GET_NEWSRC:
+ ret = (void *) myNewsrc;
+ break;
+ case SET_NEWSACTIVE:
+ if (newsActive) fs_give ((void **) &newsActive);
+ newsActive = cpystr ((char *) value);
+ case GET_NEWSACTIVE:
+ ret = (void *) newsActive;
+ break;
+ case SET_NEWSSPOOL:
+ if (newsSpool) fs_give ((void **) &newsSpool);
+ newsSpool = cpystr ((char *) value);
+ case GET_NEWSSPOOL:
+ ret = (void *) newsSpool;
+ break;
+
+ case SET_ANONYMOUSHOME:
+ if (anonymousHome) fs_give ((void **) &anonymousHome);
+ anonymousHome = cpystr ((char *) value);
+ case GET_ANONYMOUSHOME:
+ if (!anonymousHome) anonymousHome = cpystr (ANONYMOUSHOME);
+ ret = (void *) anonymousHome;
+ break;
+ case SET_FTPHOME:
+ if (ftpHome) fs_give ((void **) &ftpHome);
+ ftpHome = cpystr ((char *) value);
+ case GET_FTPHOME:
+ ret = (void *) ftpHome;
+ break;
+ case SET_PUBLICHOME:
+ if (publicHome) fs_give ((void **) &publicHome);
+ publicHome = cpystr ((char *) value);
+ case GET_PUBLICHOME:
+ ret = (void *) publicHome;
+ break;
+ case SET_SHAREDHOME:
+ if (sharedHome) fs_give ((void **) &sharedHome);
+ sharedHome = cpystr ((char *) value);
+ case GET_SHAREDHOME:
+ ret = (void *) sharedHome;
+ break;
+ case SET_SYSINBOX:
+ if (sysInbox) fs_give ((void **) &sysInbox);
+ sysInbox = cpystr ((char *) value);
+ case GET_SYSINBOX:
+ ret = (void *) sysInbox;
+ break;
+ case SET_SSLCAPATH: /* this can be set null */
+ if (sslCApath) fs_give ((void **) &sslCApath);
+ sslCApath = value ? cpystr ((char *) value) : value;
+ break;
+ case GET_SSLCAPATH:
+ ret = (void *) sslCApath;
+ break;
+ case SET_LISTMAXLEVEL:
+ list_max_level = (long) value;
+ case GET_LISTMAXLEVEL:
+ ret = (void *) list_max_level;
+ break;
+
+ case SET_MBXPROTECTION:
+ mbx_protection = (long) value;
+ case GET_MBXPROTECTION:
+ ret = (void *) mbx_protection;
+ break;
+ case SET_DIRPROTECTION:
+ dir_protection = (long) value;
+ case GET_DIRPROTECTION:
+ ret = (void *) dir_protection;
+ break;
+ case SET_LOCKPROTECTION:
+ dotlock_mode = (long) value;
+ case GET_LOCKPROTECTION:
+ ret = (void *) dotlock_mode;
+ break;
+ case SET_FTPPROTECTION:
+ ftp_protection = (long) value;
+ case GET_FTPPROTECTION:
+ ret = (void *) ftp_protection;
+ break;
+ case SET_PUBLICPROTECTION:
+ public_protection = (long) value;
+ case GET_PUBLICPROTECTION:
+ ret = (void *) public_protection;
+ break;
+ case SET_SHAREDPROTECTION:
+ shared_protection = (long) value;
+ case GET_SHAREDPROTECTION:
+ ret = (void *) shared_protection;
+ break;
+ case SET_FTPDIRPROTECTION:
+ ftp_dir_protection = (long) value;
+ case GET_FTPDIRPROTECTION:
+ ret = (void *) ftp_dir_protection;
+ break;
+ case SET_PUBLICDIRPROTECTION:
+ public_dir_protection = (long) value;
+ case GET_PUBLICDIRPROTECTION:
+ ret = (void *) public_dir_protection;
+ break;
+ case SET_SHAREDDIRPROTECTION:
+ shared_dir_protection = (long) value;
+ case GET_SHAREDDIRPROTECTION:
+ ret = (void *) shared_dir_protection;
+ break;
+
+ case SET_LOCKTIMEOUT:
+ locktimeout = (long) value;
+ case GET_LOCKTIMEOUT:
+ ret = (void *) locktimeout;
+ break;
+ case SET_DISABLEFCNTLLOCK:
+ fcntlhangbug = value ? T : NIL;
+ case GET_DISABLEFCNTLLOCK:
+ ret = (void *) (fcntlhangbug ? VOIDT : NIL);
+ break;
+ case SET_LOCKEACCESERROR:
+ disableLockWarning = value ? NIL : T;
+ case GET_LOCKEACCESERROR:
+ ret = (void *) (disableLockWarning ? NIL : VOIDT);
+ break;
+ case SET_HIDEDOTFILES:
+ hideDotFiles = value ? T : NIL;
+ case GET_HIDEDOTFILES:
+ ret = (void *) (hideDotFiles ? VOIDT : NIL);
+ break;
+ case SET_DISABLEPLAINTEXT:
+ disablePlaintext = (long) value;
+ case GET_DISABLEPLAINTEXT:
+ ret = (void *) disablePlaintext;
+ break;
+ case SET_CHROOTSERVER:
+ closedBox = value ? T : NIL;
+ case GET_CHROOTSERVER:
+ ret = (void *) (closedBox ? VOIDT : NIL);
+ break;
+ case SET_ADVERTISETHEWORLD:
+ advertisetheworld = value ? T : NIL;
+ case GET_ADVERTISETHEWORLD:
+ ret = (void *) (advertisetheworld ? VOIDT : NIL);
+ break;
+ case SET_LIMITEDADVERTISE:
+ limitedadvertise = value ? T : NIL;
+ case GET_LIMITEDADVERTISE:
+ ret = (void *) (limitedadvertise ? VOIDT : NIL);
+ break;
+ case SET_DISABLEAUTOSHAREDNS:
+ noautomaticsharedns = value ? T : NIL;
+ case GET_DISABLEAUTOSHAREDNS:
+ ret = (void *) (noautomaticsharedns ? VOIDT : NIL);
+ break;
+ case SET_DISABLE822TZTEXT:
+ no822tztext = value ? T : NIL;
+ case GET_DISABLE822TZTEXT:
+ ret = (void *) (no822tztext ? VOIDT : NIL);
+ break;
+
+ case SET_USERHASNOLIFE:
+ has_no_life = value ? T : NIL;
+ case GET_USERHASNOLIFE:
+ ret = (void *) (has_no_life ? VOIDT : NIL);
+ break;
+ case SET_KERBEROS_CP_SVR_NAME:
+ kerb_cp_svr_name = value ? T : NIL;
+ case GET_KERBEROS_CP_SVR_NAME:
+ ret = (void *) (kerb_cp_svr_name ? VOIDT : NIL);
+ break;
+ case SET_NETFSSTATBUG:
+ netfsstatbug = value ? T : NIL;
+ case GET_NETFSSTATBUG:
+ ret = (void *) (netfsstatbug ? VOIDT : NIL);
+ break;
+ case SET_BLOCKENVINIT:
+ block_env_init = value ? T : NIL;
+ case GET_BLOCKENVINIT:
+ ret = (void *) (block_env_init ? VOIDT : NIL);
+ break;
+ case SET_BLOCKNOTIFY:
+ mailblocknotify = (blocknotify_t) value;
+ case GET_BLOCKNOTIFY:
+ ret = (void *) mailblocknotify;
+ break;
+ case SET_LOGOUTHOOK:
+ maillogouthook = (logouthook_t) value;
+ case GET_LOGOUTHOOK:
+ ret = maillogouthook;
+ break;
+ case SET_LOGOUTDATA:
+ maillogoutdata = (void *) value;
+ case GET_LOGOUTDATA:
+ ret = maillogoutdata;
+ }
+ return ret;
+}
+
+/* Write current time
+ * Accepts: destination string
+ * optional format of day-of-week prefix
+ * format of date and time
+ * flag whether to append symbolic timezone
+ */
+
+static void do_date (char *date,char *prefix,char *fmt,int suffix)
+{
+ time_t tn = time (0);
+ struct tm *t = gmtime (&tn);
+ int zone = t->tm_hour * 60 + t->tm_min;
+ int julian = t->tm_yday;
+ t = localtime (&tn); /* get local time now */
+ /* minus UTC minutes since midnight */
+ zone = t->tm_hour * 60 + t->tm_min - zone;
+ /* julian can be one of:
+ * 36x local time is December 31, UTC is January 1, offset -24 hours
+ * 1 local time is 1 day ahead of UTC, offset +24 hours
+ * 0 local time is same day as UTC, no offset
+ * -1 local time is 1 day behind UTC, offset -24 hours
+ * -36x local time is January 1, UTC is December 31, offset +24 hours
+ */
+ if (julian = t->tm_yday -julian)
+ zone += ((julian < 0) == (abs (julian) == 1)) ? -24*60 : 24*60;
+ if (prefix) { /* want day of week? */
+ sprintf (date,prefix,days[t->tm_wday]);
+ date += strlen (date); /* make next sprintf append */
+ }
+ /* output the date */
+ sprintf (date,fmt,t->tm_mday,months[t->tm_mon],t->tm_year+1900,
+ t->tm_hour,t->tm_min,t->tm_sec,zone/60,abs (zone) % 60);
+ /* append timezone suffix if desired */
+ if (suffix) rfc822_timezone (date,(void *) t);
+}
+
+/* Write current time in RFC 822 format
+ * Accepts: destination string
+ */
+
+void rfc822_date (char *date)
+{
+ do_date (date,"%s, ","%d %s %d %02d:%02d:%02d %+03d%02d",
+ no822tztext ? NIL : T);
+}
+
+
+/* Write current time in fixed-width RFC 822 format
+ * Accepts: destination string
+ */
+
+void rfc822_fixed_date (char *date)
+{
+ do_date (date,NIL,"%02d %s %4d %02d:%02d:%02d %+03d%02d",NIL);
+}
+
+
+/* Write current time in internal format
+ * Accepts: destination string
+ */
+
+void internal_date (char *date)
+{
+ do_date (date,NIL,"%02d-%s-%d %02d:%02d:%02d %+03d%02d",NIL);
+}
+
+/* Initialize server
+ * Accepts: server name for syslog or NIL
+ * /etc/services service name or NIL
+ * alternate /etc/services service name or NIL
+ * clock interrupt handler
+ * kiss-of-death interrupt handler
+ * hangup interrupt handler
+ * termination interrupt handler
+ */
+
+void server_init (char *server,char *service,char *sslservice,
+ void *clkint,void *kodint,void *hupint,void *trmint,
+ void *staint)
+{
+ int onceonly = server && service && sslservice;
+ if (onceonly) { /* set server name in syslog */
+ int mask;
+ openlog (myServerName = cpystr (server),LOG_PID,syslog_facility);
+ fclose (stderr); /* possibly save a process ID */
+ dorc (NIL,NIL); /* do systemwide configuration */
+ switch (mask = umask (022)){/* check old umask */
+ case 0: /* definitely unreasonable */
+ case 022: /* don't need to change it */
+ break;
+ default: /* already was a reasonable value */
+ umask (mask); /* so change it back */
+ }
+ }
+ arm_signal (SIGALRM,clkint); /* prepare for clock interrupt */
+ arm_signal (SIGUSR2,kodint); /* prepare for Kiss Of Death */
+ arm_signal (SIGHUP,hupint); /* prepare for hangup */
+ arm_signal (SIGPIPE,hupint); /* alternative hangup */
+ arm_signal (SIGTERM,trmint); /* prepare for termination */
+ /* status dump */
+ if (staint) arm_signal (SIGUSR1,staint);
+ if (onceonly) { /* set up network and maybe SSL */
+ long port;
+ struct servent *sv;
+ /* Use SSL if SSL service, or if server starts with "s" and not service */
+ if (((port = tcp_serverport ()) >= 0)) {
+ if ((sv = getservbyname (service,"tcp")) && (port == ntohs (sv->s_port)))
+ syslog (LOG_DEBUG,"%s service init from %s",service,tcp_clientaddr ());
+ else if ((sv = getservbyname (sslservice,"tcp")) &&
+ (port == ntohs (sv->s_port))) {
+ syslog (LOG_DEBUG,"%s SSL service init from %s",sslservice,
+ tcp_clientaddr ());
+ ssl_server_init (server);
+ }
+ else { /* not service or SSL service port */
+ syslog (LOG_DEBUG,"port %ld service init from %s",port,
+ tcp_clientaddr ());
+ if (*server == 's') ssl_server_init (server);
+ }
+ }
+ }
+}
+
+/* Wait for stdin input
+ * Accepts: timeout in seconds
+ * Returns: T if have input on stdin, else NIL
+ */
+
+long server_input_wait (long seconds)
+{
+ fd_set rfd,efd;
+ struct timeval tmo;
+ FD_ZERO (&rfd);
+ FD_ZERO (&efd);
+ FD_SET (0,&rfd);
+ FD_SET (0,&efd);
+ tmo.tv_sec = seconds; tmo.tv_usec = 0;
+ return select (1,&rfd,0,&efd,&tmo) ? LONGT : NIL;
+}
+
+/* Return UNIX password entry for user name
+ * Accepts: user name string
+ * Returns: password entry
+ *
+ * Tries all-lowercase form of user name if given user name fails
+ */
+
+static struct passwd *pwuser (unsigned char *user)
+{
+ unsigned char *s;
+ struct passwd *pw = getpwnam (user);
+ if (!pw) { /* failed, see if any uppercase characters */
+ for (s = user; *s && ((*s < 'A') || (*s > 'Z')); s++);
+ if (*s) { /* yes, try all lowercase form */
+ pw = getpwnam (s = lcase (cpystr (user)));
+ fs_give ((void **) &s);
+ }
+ }
+ return pw;
+}
+
+
+/* Validate password for user name
+ * Accepts: user name string
+ * password string
+ * argument count
+ * argument vector
+ * Returns: password entry if validated
+ *
+ * Tries password+1 if password fails and starts with space
+ */
+
+static struct passwd *valpwd (char *user,char *pwd,int argc,char *argv[])
+{
+ char *s;
+ struct passwd *pw;
+ struct passwd *ret = NIL;
+ if (auth_md5.server) { /* using CRAM-MD5 authentication? */
+ if (s = auth_md5_pwd (user)) {
+ if (!strcmp (s,pwd) || ((*pwd == ' ') && pwd[1] && !strcmp (s,pwd+1)))
+ ret = pwuser (user); /* validated, get passwd entry for user */
+ memset (s,0,strlen (s)); /* erase sensitive information */
+ fs_give ((void **) &s);
+ }
+ }
+ else if (pw = pwuser (user)) {/* can get user? */
+ s = cpystr (pw->pw_name); /* copy returned name in case we need it */
+ if (*pwd && !(ret = checkpw (pw,pwd,argc,argv)) &&
+ (*pwd == ' ') && pwd[1] && (ret = pwuser (s)))
+ ret = checkpw (pw,pwd+1,argc,argv);
+ fs_give ((void **) &s); /* don't need copy of name any more */
+ }
+ return ret;
+}
+
+/* Server log in
+ * Accepts: user name string
+ * password string
+ * authenticating user name string
+ * argument count
+ * argument vector
+ * Returns: T if password validated, NIL otherwise
+ */
+
+long server_login (char *user,char *pwd,char *authuser,int argc,char *argv[])
+{
+ struct passwd *pw = NIL;
+ int level = LOG_NOTICE;
+ char *err = "failed";
+ /* cretins still haven't given up */
+ if ((strlen (user) >= NETMAXUSER) ||
+ (authuser && (strlen (authuser) >= NETMAXUSER))) {
+ level = LOG_ALERT; /* escalate this alert */
+ err = "SYSTEM BREAK-IN ATTEMPT";
+ logtry = 0; /* render this session useless */
+ }
+ else if (logtry-- <= 0) err = "excessive login failures";
+ else if (disablePlaintext) err = "disabled";
+ else if (!(authuser && *authuser)) pw = valpwd (user,pwd,argc,argv);
+ else if (valpwd (authuser,pwd,argc,argv)) pw = pwuser (user);
+ if (pw && pw_login (pw,authuser,pw->pw_name,NIL,argc,argv)) return T;
+ syslog (level|LOG_AUTH,"Login %s user=%.64s auth=%.64s host=%.80s",err,
+ user,(authuser && *authuser) ? authuser : user,tcp_clienthost ());
+ sleep (3); /* slow down possible cracker */
+ return NIL;
+}
+
+/* Authenticated server log in
+ * Accepts: user name string
+ * authenticating user name string
+ * argument count
+ * argument vector
+ * Returns: T if password validated, NIL otherwise
+ */
+
+long authserver_login (char *user,char *authuser,int argc,char *argv[])
+{
+ return pw_login (pwuser (user),authuser,user,NIL,argc,argv);
+}
+
+
+/* Log in as anonymous daemon
+ * Accepts: argument count
+ * argument vector
+ * Returns: T if successful, NIL if error
+ */
+
+long anonymous_login (int argc,char *argv[])
+{
+ /* log in Mr. A. N. Onymous */
+ return pw_login (getpwnam (ANONYMOUSUSER),NIL,NIL,
+ (char *) mail_parameters (NIL,GET_ANONYMOUSHOME,NIL),
+ argc,argv);
+}
+
+/* Finish log in and environment initialization
+ * Accepts: passwd struct for loginpw()
+ * optional authentication user name
+ * user name (NIL for anonymous)
+ * home directory (NIL to use directory from passwd struct)
+ * argument count
+ * argument vector
+ * Returns: T if successful, NIL if error
+ */
+
+long pw_login (struct passwd *pw,char *auser,char *user,char *home,int argc,
+ char *argv[])
+{
+ struct group *gr;
+ char **t;
+ long ret = NIL;
+ if (pw && pw->pw_uid) { /* must have passwd struct for non-UID 0 */
+ /* make safe copies of user and home */
+ if (user) user = cpystr (pw->pw_name);
+ home = cpystr (home ? home : pw->pw_dir);
+ /* authorization ID .NE. authentication ID? */
+ if (user && auser && *auser && compare_cstring (auser,user)) {
+ /* scan list of mail administrators */
+ if ((gr = getgrnam (ADMINGROUP)) && (t = gr->gr_mem)) while (*t && !ret)
+ if (!compare_cstring (auser,*t++))
+ ret = pw_login (pw,NIL,user,home,argc,argv);
+ syslog (LOG_NOTICE|LOG_AUTH,"%s %.80s override of user=%.80s host=%.80s",
+ ret ? "Admin" : "Failed",auser,user,tcp_clienthost ());
+ }
+ else if (closedBox) { /* paranoid site, lock out other directories */
+ if (chdir (home) || chroot (home))
+ syslog (LOG_NOTICE|LOG_AUTH,
+ "Login %s failed: unable to set chroot=%.80s host=%.80s",
+ pw->pw_name,home,tcp_clienthost ());
+ else if (loginpw (pw,argc,argv)) ret = env_init (user,NIL);
+ else fatal ("Login failed after chroot");
+ }
+ /* normal login */
+ else if (((pw->pw_uid == geteuid ()) || loginpw (pw,argc,argv)) &&
+ (ret = env_init (user,home))) chdir (myhomedir ());
+ fs_give ((void **) &home); /* clean up */
+ if (user) fs_give ((void **) &user);
+ }
+ endpwent (); /* in case shadow passwords in pw data */
+ return ret; /* return status */
+}
+
+/* Initialize environment
+ * Accepts: user name (NIL for anonymous)
+ * home directory name
+ * Returns: T, always
+ */
+
+long env_init (char *user,char *home)
+{
+ extern MAILSTREAM CREATEPROTO;
+ extern MAILSTREAM EMPTYPROTO;
+ struct passwd *pw;
+ struct stat sbuf;
+ char tmp[MAILTMPLEN];
+ /* don't init if blocked */
+ if (block_env_init) return LONGT;
+ if (myUserName) fatal ("env_init called twice!");
+ /* initially nothing in namespace list */
+ nslist[0] = nslist[1] = nslist[2] = NIL;
+ /* myUserName must be set before dorc() call */
+ myUserName = cpystr (user ? user : ANONYMOUSUSER);
+ /* force default prototypes to be set */
+ if (!createProto) createProto = &CREATEPROTO;
+ if (!appendProto) appendProto = &EMPTYPROTO;
+ dorc (NIL,NIL); /* do systemwide configuration */
+ if (!home) { /* closed box server */
+ /* standard user can only reference home */
+ if (user) nslist[0] = &nshome;
+ else { /* anonymous user */
+ nslist[0] = &nsblackother; /* set root */
+ anonymous = T; /* flag as anonymous */
+ }
+ myHomeDir = cpystr (""); /* home directory is root */
+ sysInbox = cpystr ("INBOX");/* make system INBOX */
+ }
+ else { /* open or black box */
+ closedBox = NIL; /* definitely not a closed box */
+ if (user) { /* remember user name and home directory */
+ if (blackBoxDir) { /* build black box directory name */
+ sprintf (tmp,"%s/%s",blackBoxDir,myUserName);
+ /* must exist */
+ if (!((!stat (home = tmp,&sbuf) && (sbuf.st_mode & S_IFDIR)) ||
+ (blackBoxDefaultHome &&
+ !stat (home = blackBoxDefaultHome,&sbuf) &&
+ (sbuf.st_mode & S_IFDIR)))) fatal ("no home");
+ sysInbox = (char *) fs_get (strlen (home) + 7);
+ /* set system INBOX */
+ sprintf (sysInbox,"%s/INBOX",home);
+ blackBox = T; /* mark that it's a black box */
+ /* mbox meaningless if black box */
+ mail_parameters (NIL,DISABLE_DRIVER,(void *) "mbox");
+ }
+ nslist[0] = &nshome; /* home namespace */
+ /* limited advertise namespaces */
+ if (limitedadvertise) nslist[2] = &nslimited;
+ else if (blackBox) { /* black box namespaces */
+ nslist[1] = &nsblackother;
+ nslist[2] = &nsshared;
+ }
+ else { /* open box namespaces */
+ nslist[1] = &nsunixother;
+ nslist[2] = advertisetheworld ? &nsworld : &nsshared;
+ }
+ }
+ else {
+ nslist[2] = &nsftp; /* anonymous user */
+ sprintf (tmp,"%s/INBOX",
+ home = (char *) mail_parameters (NIL,GET_ANONYMOUSHOME,NIL));
+ sysInbox = cpystr (tmp); /* make system INBOX */
+ anonymous = T; /* flag as anonymous */
+ }
+ myHomeDir = cpystr (home); /* set home directory */
+ }
+
+ if (allowuserconfig) { /* allow user config files */
+ dorc (strcat (strcpy (tmp,myHomeDir),"/.mminit"),T);
+ dorc (strcat (strcpy (tmp,myHomeDir),"/.imaprc"),NIL);
+ }
+ if (!closedBox && !noautomaticsharedns) {
+ /* #ftp namespace */
+ if (!ftpHome && (pw = getpwnam ("ftp"))) ftpHome = cpystr (pw->pw_dir);
+ /* #public namespace */
+ if (!publicHome && (pw = getpwnam ("imappublic")))
+ publicHome = cpystr (pw->pw_dir);
+ /* #shared namespace */
+ if (!anonymous && !sharedHome && (pw = getpwnam ("imapshared")))
+ sharedHome = cpystr (pw->pw_dir);
+ }
+ if (!myLocalHost) mylocalhost ();
+ if (!myNewsrc) myNewsrc = cpystr(strcat (strcpy (tmp,myHomeDir),"/.newsrc"));
+ if (!newsActive) newsActive = cpystr (ACTIVEFILE);
+ if (!newsSpool) newsSpool = cpystr (NEWSSPOOL);
+ /* re-do open action to get flags */
+ (*createProto->dtb->open) (NIL);
+ endpwent (); /* close pw database */
+ return T;
+}
+
+/* Return my user name
+ * Accepts: pointer to optional flags
+ * Returns: my user name
+ */
+
+char *myusername_full (unsigned long *flags)
+{
+ struct passwd *pw;
+ struct stat sbuf;
+ char *s;
+ unsigned long euid;
+ char *ret = UNLOGGEDUSER;
+ /* no user name yet and not root? */
+ if (!myUserName && (euid = geteuid ())) {
+ /* yes, look up getlogin() user name or EUID */
+ if (((s = (char *) getlogin ()) && *s && (strlen (s) < NETMAXUSER) &&
+ (pw = getpwnam (s)) && (pw->pw_uid == euid)) ||
+ (pw = getpwuid (euid))) {
+ if (block_env_init) { /* don't env_init if blocked */
+ if (flags) *flags = MU_LOGGEDIN;
+ return pw->pw_name;
+ }
+ env_init (pw->pw_name,
+ ((s = getenv ("HOME")) && *s && (strlen (s) < NETMAXMBX) &&
+ !stat (s,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) ?
+ s : pw->pw_dir);
+ }
+ else fatal ("Unable to look up user name");
+ }
+ if (myUserName) { /* logged in? */
+ if (flags) *flags = anonymous ? MU_ANONYMOUS : MU_LOGGEDIN;
+ ret = myUserName; /* return user name */
+ }
+ else if (flags) *flags = MU_NOTLOGGEDIN;
+ return ret;
+}
+
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost ()
+{
+ if (!myLocalHost) {
+ char *s,tmp[MAILTMPLEN];
+ char *t = "unknown";
+ tmp[0] = tmp[MAILTMPLEN-1] = '\0';
+ if (!gethostname (tmp,MAILTMPLEN-1) && tmp[0]) {
+ /* sanity check of name */
+ for (s = tmp; (*s > 0x20) && (*s < 0x7f); ++s);
+ if (!*s) t = tcp_canonical (tmp);
+ }
+ myLocalHost = cpystr (t);
+ }
+ return myLocalHost;
+}
+
+/* Return my home directory name
+ * Returns: my home directory name
+ */
+
+char *myhomedir ()
+{
+ if (!myHomeDir) myusername ();/* initialize if first time */
+ return myHomeDir ? myHomeDir : "";
+}
+
+
+/* Return my home mailbox name
+ * Returns: my home directory name
+ */
+
+static char *mymailboxdir ()
+{
+ char *home = myhomedir ();
+ /* initialize if first time */
+ if (!myMailboxDir && myHomeDir) {
+ if (mailsubdir) {
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"%s/%s",home,mailsubdir);
+ myMailboxDir = cpystr (tmp);/* use pre-defined subdirectory of home */
+ }
+ else myMailboxDir = cpystr (home);
+ }
+ return myMailboxDir ? myMailboxDir : "";
+}
+
+
+/* Return system standard INBOX
+ * Accepts: buffer string
+ */
+
+char *sysinbox ()
+{
+ char tmp[MAILTMPLEN];
+ if (!sysInbox) { /* initialize if first time */
+ sprintf (tmp,"%s/%s",MAILSPOOL,myusername ());
+ sysInbox = cpystr (tmp); /* system inbox is from mail spool */
+ }
+ return sysInbox;
+}
+
+/* Return mailbox directory name
+ * Accepts: destination buffer
+ * directory prefix
+ * name in directory
+ * Returns: file name or NIL if error
+ */
+
+char *mailboxdir (char *dst,char *dir,char *name)
+{
+ char tmp[MAILTMPLEN];
+ if (dir || name) { /* if either argument provided */
+ if (dir) {
+ if (strlen (dir) > NETMAXMBX) return NIL;
+ strcpy (tmp,dir); /* write directory prefix */
+ }
+ else tmp[0] = '\0'; /* otherwise null string */
+ if (name) {
+ if (strlen (name) > NETMAXMBX) return NIL;
+ strcat (tmp,name); /* write name in directory */
+ }
+ /* validate name, return its name */
+ if (!mailboxfile (dst,tmp)) return NIL;
+ }
+ /* no arguments, wants mailbox directory */
+ else strcpy (dst,mymailboxdir ());
+ return dst; /* return the name */
+}
+
+/* Return mailbox file name
+ * Accepts: destination buffer
+ * mailbox name
+ * Returns: file name or empty string for driver-selected INBOX or NIL if error
+ */
+
+char *mailboxfile (char *dst,char *name)
+{
+ struct passwd *pw;
+ char *s;
+ if (!name || !*name || (*name == '{') || (strlen (name) > NETMAXMBX) ||
+ ((anonymous || blackBox || restrictBox || (*name == '#')) &&
+ (strstr (name,"..") || strstr (name,"//") || strstr (name,"/~"))))
+ dst = NIL; /* invalid name */
+ else switch (*name) { /* determine mailbox type based upon name */
+ case '#': /* namespace name */
+ /* #ftp/ namespace */
+ if (((name[1] == 'f') || (name[1] == 'F')) &&
+ ((name[2] == 't') || (name[2] == 'T')) &&
+ ((name[3] == 'p') || (name[3] == 'P')) &&
+ (name[4] == '/') && ftpHome) sprintf (dst,"%s/%s",ftpHome,name+5);
+ /* #public/ and #shared/ namespaces */
+ else if ((((name[1] == 'p') || (name[1] == 'P')) &&
+ ((name[2] == 'u') || (name[2] == 'U')) &&
+ ((name[3] == 'b') || (name[3] == 'B')) &&
+ ((name[4] == 'l') || (name[4] == 'L')) &&
+ ((name[5] == 'i') || (name[5] == 'I')) &&
+ ((name[6] == 'c') || (name[6] == 'C')) &&
+ (name[7] == '/') && (s = publicHome)) ||
+ (!anonymous && ((name[1] == 's') || (name[1] == 'S')) &&
+ ((name[2] == 'h') || (name[2] == 'H')) &&
+ ((name[3] == 'a') || (name[3] == 'A')) &&
+ ((name[4] == 'r') || (name[4] == 'R')) &&
+ ((name[5] == 'e') || (name[5] == 'E')) &&
+ ((name[6] == 'd') || (name[6] == 'D')) &&
+ (name[7] == '/') && (s = sharedHome)))
+ sprintf (dst,"%s/%s",s,compare_cstring (name+8,"INBOX") ?
+ name+8 : "INBOX");
+ else dst = NIL; /* unknown namespace */
+ break;
+
+ case '/': /* root access */
+ if (anonymous) dst = NIL; /* anonymous forbidden to do this */
+ else if (blackBox) { /* other user access if blackbox */
+ if (restrictBox & RESTRICTOTHERUSER) dst = NIL;
+ /* see if other user INBOX */
+ else if ((s = strchr (name+1,'/')) && !compare_cstring (s+1,"INBOX")) {
+ *s = '\0'; /* temporarily tie off string */
+ sprintf (dst,"%s/%s/INBOX",blackBoxDir,name+1);
+ *s = '/'; /* in case caller cares */
+ }
+ else sprintf (dst,"%s/%s",blackBoxDir,name+1);
+ }
+ else if ((restrictBox & RESTRICTROOT) && strcmp (name,sysinbox ()))
+ dst = NIL; /* restricted and not access to sysinbox */
+ else strcpy (dst,name); /* unrestricted, copy root name */
+ break;
+ case '~': /* other user access */
+ /* bad syntax or anonymous can't win */
+ if (!*++name || anonymous) dst = NIL;
+ /* ~/ equivalent to ordinary name */
+ else if (*name == '/') sprintf (dst,"%s/%s",mymailboxdir (),name+1);
+ /* other user forbidden if closed/restricted */
+ else if (closedBox || (restrictBox & RESTRICTOTHERUSER)) dst = NIL;
+ else if (blackBox) { /* black box form of other user */
+ /* see if other user INBOX */
+ if ((s = strchr (name,'/')) && compare_cstring (s+1,"INBOX")) {
+ *s = '\0'; /* temporarily tie off string */
+ sprintf (dst,"%s/%s/INBOX",blackBoxDir,name);
+ *s = '/'; /* in case caller cares */
+ }
+ else sprintf (dst,"%s/%s",blackBoxDir,name);
+ }
+ else { /* clear box other user */
+ /* copy user name */
+ for (s = dst; *name && (*name != '/'); *s++ = *name++);
+ *s++ = '\0'; /* tie off user name, look up in passwd file */
+ if ((pw = getpwnam (dst)) && pw->pw_dir) {
+ if (*name) name++; /* skip past the slash */
+ /* canonicalize case of INBOX */
+ if (!compare_cstring (name,"INBOX")) name = "INBOX";
+ /* remove trailing / from directory */
+ if ((s = strrchr (pw->pw_dir,'/')) && !s[1]) *s = '\0';
+ /* don't allow ~root/ if restricted root */
+ if ((restrictBox & RESTRICTROOT) && !*pw->pw_dir) dst = NIL;
+ /* build final name w/ subdir if needed */
+ else if (mailsubdir) sprintf (dst,"%s/%s/%s",pw->pw_dir,mailsubdir,name);
+ else sprintf (dst,"%s/%s",pw->pw_dir,name);
+ }
+ else dst = NIL; /* no such user */
+ }
+ break;
+
+ case 'I': case 'i': /* possible INBOX */
+ if (!compare_cstring (name+1,"NBOX")) {
+ /* if restricted, use INBOX in mailbox dir */
+ if (anonymous || blackBox || closedBox)
+ sprintf (dst,"%s/INBOX",mymailboxdir ());
+ else *dst = '\0'; /* otherwise driver selects the name */
+ break;
+ }
+ /* drop into to ordinary name case */
+ default: /* ordinary name is easy */
+ sprintf (dst,"%s/%s",mymailboxdir (),name);
+ break;
+ }
+ return dst; /* return final name */
+}
+
+/* Dot-lock file locker
+ * Accepts: file name to lock
+ * destination buffer for lock file name
+ * open file description on file name to lock
+ * Returns: T if success, NIL if failure
+ */
+
+long dotlock_lock (char *file,DOTLOCK *base,int fd)
+{
+ int i = locktimeout * 60;
+ int j,mask,retry,pi[2],po[2];
+ char *s,tmp[MAILTMPLEN];
+ struct stat sb;
+ /* flush absurd file name */
+ if (strlen (file) > 512) return NIL;
+ /* build lock filename */
+ sprintf (base->lock,"%s.lock",file);
+ /* assume no pipe */
+ base->pipei = base->pipeo = -1;
+ do { /* make sure not symlink */
+ if (!(j = chk_notsymlink (base->lock,&sb))) return NIL;
+ /* time out if file older than 5 minutes */
+ if ((j > 0) && ((time (0)) >= (sb.st_ctime + locktimeout * 60))) i = 0;
+ /* try to create the lock */
+ switch (retry = crexcl (base->lock)) {
+ case -1: /* OK to retry */
+ if (!(i%15)) { /* time to notify? */
+ sprintf (tmp,"Mailbox %.80s is locked, will override in %d seconds...",
+ file,i);
+ MM_LOG (tmp,WARN);
+ }
+ sleep (1); /* wait 1 second before next try */
+ break;
+ case NIL: /* failure, can't retry */
+ i = 0;
+ break;
+ case T: /* success, make sure others can break lock */
+ chmod (base->lock,(int) dotlock_mode);
+ return LONGT;
+ }
+ } while (i--); /* until out of retries */
+ if (retry < 0) { /* still returning retry after locktimeout? */
+ if (!(j = chk_notsymlink (base->lock,&sb))) return NIL;
+ if ((j > 0) && ((time (0)) < (sb.st_ctime + locktimeout * 60))) {
+ sprintf (tmp,"Mailbox vulnerable - seizing %ld second old lock",
+ (long) (time (0) - sb.st_ctime));
+ MM_LOG (tmp,WARN);
+ }
+ mask = umask (0); /* want our lock protection */
+ unlink (base->lock); /* try to remove the old file */
+ /* seize the lock */
+ if ((i = open (base->lock,O_WRONLY|O_CREAT,(int) dotlock_mode)) >= 0) {
+ close (i); /* don't need descriptor any more */
+ sprintf (tmp,"Mailbox %.80s lock overridden",file);
+ MM_LOG (tmp,NIL);
+ chmod (base->lock,(int) dotlock_mode);
+ umask (mask); /* restore old umask */
+ return LONGT;
+ }
+ umask (mask); /* restore old umask */
+ }
+
+ if (fd >= 0) switch (errno) {
+ case EACCES: /* protection failure? */
+ MM_CRITICAL (NIL); /* go critical */
+ if (closedBox || !lockpgm); /* can't do on closed box or disabled */
+ else if ((*lockpgm && stat (lockpgm,&sb)) ||
+ (!*lockpgm && stat (lockpgm = LOCKPGM1,&sb) &&
+ stat (lockpgm = LOCKPGM2,&sb) && stat (lockpgm = LOCKPGM3,&sb)))
+ lockpgm = NIL; /* disable if can't find lockpgm */
+ else if (pipe (pi) >= 0) { /* make command pipes */
+ long cf;
+ char *argv[4],arg[20];
+ /* if input pipes usable create output pipes */
+ if ((pi[0] < FD_SETSIZE) && (pi[1] < FD_SETSIZE) && (pipe (po) >= 0)) {
+ /* make sure output pipes are usable */
+ if ((po[0] >= FD_SETSIZE) || (po[1] >= FD_SETSIZE));
+ /* all is good, make inferior process */
+ else if (!(j = fork ())) {
+ if (!fork ()) { /* make grandchild so it's inherited by init */
+ /* prepare argument vector */
+ sprintf (arg,"%d",fd);
+ argv[0] = lockpgm; argv[1] = arg;
+ argv[2] = file; argv[3] = NIL;
+ /* set parent's I/O to my O/I */
+ dup2 (pi[1],1); dup2 (pi[1],2); dup2 (po[0],0);
+ /* close all unnecessary descriptors */
+ for (cf = max (20,max (max (pi[0],pi[1]),max(po[0],po[1])));
+ cf >= 3; --cf) if (cf != fd) close (cf);
+ /* be our own process group */
+ setpgrp (0,getpid ());
+ /* now run it */
+ _exit (execv (argv[0],argv));
+ }
+ _exit (1); /* child is done */
+ }
+ else if (j > 0) { /* parent process */
+ fd_set rfd;
+ struct timeval tmo;
+ FD_ZERO (&rfd);
+ FD_SET (pi[0],&rfd);
+ tmo.tv_sec = locktimeout * 60;
+ grim_pid_reap (j,NIL);/* reap child; grandchild now owned by init */
+ /* read response from locking program */
+ if (select (pi[0]+1,&rfd,0,0,&tmo) &&
+ (read (pi[0],tmp,1) == 1) && (tmp[0] == '+')) {
+ /* success, record pipes */
+ base->pipei = pi[0]; base->pipeo = po[1];
+ /* close child's side of the pipes */
+ close (pi[1]); close (po[0]);
+ MM_NOCRITICAL (NIL);/* no longer critical */
+ return LONGT;
+ }
+ }
+ close (po[0]); close (po[1]);
+ }
+ close (pi[0]); close (pi[1]);
+ }
+
+ MM_NOCRITICAL (NIL); /* no longer critical */
+ /* find directory/file delimiter */
+ if (s = strrchr (base->lock,'/')) {
+ *s = '\0'; /* tie off at directory */
+ sprintf(tmp, /* generate default message */
+ "Mailbox vulnerable - directory %.80s must have 1777 protection",
+ base->lock);
+ /* definitely not 1777 if can't stat */
+ mask = stat (base->lock,&sb) ? 0 : (sb.st_mode & 1777);
+ *s = '/'; /* restore lock name */
+ if (mask != 1777) { /* default warning if not 1777 */
+ if (!disableLockWarning) MM_LOG (tmp,WARN);
+ break;
+ }
+ }
+ default:
+ sprintf (tmp,"Mailbox vulnerable - error creating %.80s: %s",
+ base->lock,strerror (errno));
+ if (!disableLockWarning) MM_LOG (tmp,WARN);
+ break;
+ }
+ base->lock[0] = '\0'; /* don't use lock files */
+ return NIL;
+}
+
+/* Dot-lock file unlocker
+ * Accepts: lock file name
+ * Returns: T if success, NIL if failure
+ */
+
+long dotlock_unlock (DOTLOCK *base)
+{
+ long ret = LONGT;
+ if (base && base->lock[0]) {
+ if (base->pipei >= 0) { /* if running through a pipe unlocker */
+ ret = (write (base->pipeo,"+",1) == 1);
+ /* nuke the pipes */
+ close (base->pipei); close (base->pipeo);
+ }
+ else ret = !unlink (base->lock);
+ }
+ return ret;
+}
+
+/* Lock file name
+ * Accepts: scratch buffer
+ * file name
+ * type of locking operation (LOCK_SH or LOCK_EX)
+ * pointer to return PID of locker
+ * Returns: file descriptor of lock or negative if error
+ */
+
+int lockname (char *lock,char *fname,int op,long *pid)
+{
+ struct stat sbuf;
+ *pid = 0; /* no locker PID */
+ return stat (fname,&sbuf) ? -1 : lock_work (lock,&sbuf,op,pid);
+}
+
+
+/* Lock file descriptor
+ * Accepts: file descriptor
+ * lock file name buffer
+ * type of locking operation (LOCK_SH or LOCK_EX)
+ * Returns: file descriptor of lock or negative if error
+ */
+
+int lockfd (int fd,char *lock,int op)
+{
+ struct stat sbuf;
+ return fstat (fd,&sbuf) ? -1 : lock_work (lock,&sbuf,op,NIL);
+}
+
+/* Lock file name worker
+ * Accepts: lock file name
+ * pointer to stat() buffer
+ * type of locking operation (LOCK_SH or LOCK_EX)
+ * pointer to return PID of locker
+ * Returns: file descriptor of lock or negative if error
+ */
+
+int lock_work (char *lock,void *sb,int op,long *pid)
+{
+ struct stat lsb,fsb;
+ struct stat *sbuf = (struct stat *) sb;
+ char tmp[MAILTMPLEN];
+ long i;
+ int fd;
+ int mask = umask (0);
+ if (pid) *pid = 0; /* initialize return PID */
+ /* make temporary lock file name */
+ sprintf (lock,"%s/.%lx.%lx",closedBox ? "" : tmpdir,
+ (unsigned long) sbuf->st_dev,(unsigned long) sbuf->st_ino);
+ while (T) { /* until get a good lock */
+ do switch ((int) chk_notsymlink (lock,&lsb)) {
+ case 1: /* exists just once */
+ if (((fd = open (lock,O_RDWR,shlock_mode)) >= 0) ||
+ (errno != ENOENT) || (chk_notsymlink (lock,&lsb) >= 0)) break;
+ case -1: /* name doesn't exist */
+ fd = open (lock,O_RDWR|O_CREAT|O_EXCL,shlock_mode);
+ break;
+ default: /* multiple hard links */
+ MM_LOG ("hard link to lock name",ERROR);
+ syslog (LOG_CRIT,"SECURITY PROBLEM: hard link to lock name: %.80s",lock);
+ case 0: /* symlink (already did syslog) */
+ umask (mask); /* restore old mask */
+ return -1; /* fail: no lock file */
+ } while ((fd < 0) && (errno == EEXIST));
+ if (fd < 0) { /* failed to get file descriptor */
+ syslog (LOG_INFO,"Mailbox lock file %s open failure: %s",lock,
+ strerror (errno));
+ if (!closedBox) { /* more explicit snarl for bad configuration */
+ if (stat (tmpdir,&lsb))
+ syslog (LOG_CRIT,"SYSTEM ERROR: no %s: %s",tmpdir,strerror (errno));
+ else if ((lsb.st_mode & 01777) != 01777) {
+ sprintf (tmp,"Can't lock for write: %.80s must have 1777 protection",
+ tmpdir);
+ MM_LOG (tmp,WARN);
+ }
+ }
+ umask (mask); /* restore old mask */
+ return -1; /* fail: can't open lock file */
+ }
+
+ /* non-blocking form */
+ if (op & LOCK_NB) i = flock (fd,op);
+ else { /* blocking form */
+ (*mailblocknotify) (BLOCK_FILELOCK,NIL);
+ i = flock (fd,op);
+ (*mailblocknotify) (BLOCK_NONE,NIL);
+ }
+ if (i) { /* failed, get other process' PID */
+ if (pid && !fstat (fd,&fsb) && (i = min (fsb.st_size,MAILTMPLEN-1)) &&
+ (read (fd,tmp,i) == i) && !(tmp[i] = 0) && ((i = atol (tmp)) > 0))
+ *pid = i;
+ close (fd); /* failed, give up on lock */
+ umask (mask); /* restore old mask */
+ return -1; /* fail: can't lock */
+ }
+ /* make sure this lock is good for us */
+ if (!lstat (lock,&lsb) && ((lsb.st_mode & S_IFMT) != S_IFLNK) &&
+ !fstat (fd,&fsb) && (lsb.st_dev == fsb.st_dev) &&
+ (lsb.st_ino == fsb.st_ino) && (fsb.st_nlink == 1)) break;
+ close (fd); /* lock not right, drop fd and try again */
+ }
+ chmod (lock,shlock_mode); /* make sure mode OK (don't use fchmod()) */
+ umask (mask); /* restore old mask */
+ return fd; /* success */
+}
+
+/* Check to make sure not a symlink
+ * Accepts: file name
+ * stat buffer
+ * Returns: -1 if doesn't exist, NIL if symlink, else number of hard links
+ */
+
+long chk_notsymlink (char *name,void *sb)
+{
+ struct stat *sbuf = (struct stat *) sb;
+ /* name exists? */
+ if (lstat (name,sbuf)) return -1;
+ /* forbid symbolic link */
+ if ((sbuf->st_mode & S_IFMT) == S_IFLNK) {
+ MM_LOG ("symbolic link on lock name",ERROR);
+ syslog (LOG_CRIT,"SECURITY PROBLEM: symbolic link on lock name: %.80s",
+ name);
+ return NIL;
+ }
+ return (long) sbuf->st_nlink; /* return number of hard links */
+}
+
+
+/* Unlock file descriptor
+ * Accepts: file descriptor
+ * lock file name from lockfd()
+ */
+
+void unlockfd (int fd,char *lock)
+{
+ /* delete the file if no sharers */
+ if (!flock (fd,LOCK_EX|LOCK_NB)) unlink (lock);
+ flock (fd,LOCK_UN); /* unlock it */
+ close (fd); /* close it */
+}
+
+/* Set proper file protection for mailbox
+ * Accepts: mailbox name
+ * actual file path name
+ * Returns: T, always
+ */
+
+long set_mbx_protections (char *mailbox,char *path)
+{
+ struct stat sbuf;
+ int mode = (int) mbx_protection;
+ if (*mailbox == '#') { /* possible namespace? */
+ if (((mailbox[1] == 'f') || (mailbox[1] == 'F')) &&
+ ((mailbox[2] == 't') || (mailbox[2] == 'T')) &&
+ ((mailbox[3] == 'p') || (mailbox[3] == 'P')) &&
+ (mailbox[4] == '/')) mode = (int) ftp_protection;
+ else if (((mailbox[1] == 'p') || (mailbox[1] == 'P')) &&
+ ((mailbox[2] == 'u') || (mailbox[2] == 'U')) &&
+ ((mailbox[3] == 'b') || (mailbox[3] == 'B')) &&
+ ((mailbox[4] == 'l') || (mailbox[4] == 'L')) &&
+ ((mailbox[5] == 'i') || (mailbox[5] == 'I')) &&
+ ((mailbox[6] == 'c') || (mailbox[6] == 'C')) &&
+ (mailbox[7] == '/')) mode = (int) public_protection;
+ else if (((mailbox[1] == 's') || (mailbox[1] == 'S')) &&
+ ((mailbox[2] == 'h') || (mailbox[2] == 'H')) &&
+ ((mailbox[3] == 'a') || (mailbox[3] == 'A')) &&
+ ((mailbox[4] == 'r') || (mailbox[4] == 'R')) &&
+ ((mailbox[5] == 'e') || (mailbox[5] == 'E')) &&
+ ((mailbox[6] == 'd') || (mailbox[6] == 'D')) &&
+ (mailbox[7] == '/')) mode = (int) shared_protection;
+ }
+ /* if a directory */
+ if (!stat (path,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
+ /* set owner search if allow read or write */
+ if (mode & 0600) mode |= 0100;
+ if (mode & 060) mode |= 010;/* set group search if allow read or write */
+ if (mode & 06) mode |= 01; /* set world search if allow read or write */
+ /* preserve directory SGID bit */
+ if (sbuf.st_mode & S_ISGID) mode |= S_ISGID;
+ }
+ chmod (path,mode); /* set the new protection, ignore failure */
+ return LONGT;
+}
+
+/* Get proper directory protection
+ * Accepts: mailbox name
+ * Returns: directory mode, always
+ */
+
+long get_dir_protection (char *mailbox)
+{
+ if (*mailbox == '#') { /* possible namespace? */
+ if (((mailbox[1] == 'f') || (mailbox[1] == 'F')) &&
+ ((mailbox[2] == 't') || (mailbox[2] == 'T')) &&
+ ((mailbox[3] == 'p') || (mailbox[3] == 'P')) &&
+ (mailbox[4] == '/')) return ftp_dir_protection;
+ else if (((mailbox[1] == 'p') || (mailbox[1] == 'P')) &&
+ ((mailbox[2] == 'u') || (mailbox[2] == 'U')) &&
+ ((mailbox[3] == 'b') || (mailbox[3] == 'B')) &&
+ ((mailbox[4] == 'l') || (mailbox[4] == 'L')) &&
+ ((mailbox[5] == 'i') || (mailbox[5] == 'I')) &&
+ ((mailbox[6] == 'c') || (mailbox[6] == 'C')) &&
+ (mailbox[7] == '/')) return public_dir_protection;
+ else if (((mailbox[1] == 's') || (mailbox[1] == 'S')) &&
+ ((mailbox[2] == 'h') || (mailbox[2] == 'H')) &&
+ ((mailbox[3] == 'a') || (mailbox[3] == 'A')) &&
+ ((mailbox[4] == 'r') || (mailbox[4] == 'R')) &&
+ ((mailbox[5] == 'e') || (mailbox[5] == 'E')) &&
+ ((mailbox[6] == 'd') || (mailbox[6] == 'D')) &&
+ (mailbox[7] == '/')) return shared_dir_protection;
+ }
+ return dir_protection;
+}
+
+/* Determine default prototype stream to user
+ * Accepts: type (NIL for create, T for append)
+ * Returns: default prototype stream
+ */
+
+MAILSTREAM *default_proto (long type)
+{
+ myusername (); /* make sure initialized */
+ /* return default driver's prototype */
+ return type ? appendProto : createProto;
+}
+
+
+/* Set up user flags for stream
+ * Accepts: MAIL stream
+ * Returns: MAIL stream with user flags set up
+ */
+
+MAILSTREAM *user_flags (MAILSTREAM *stream)
+{
+ int i;
+ myusername (); /* make sure initialized */
+ for (i = 0; i < NUSERFLAGS && userFlags[i]; ++i)
+ if (!stream->user_flags[i]) stream->user_flags[i] = cpystr (userFlags[i]);
+ return stream;
+}
+
+
+/* Return nth user flag
+ * Accepts: user flag number
+ * Returns: flag
+ */
+
+char *default_user_flag (unsigned long i)
+{
+ myusername (); /* make sure initialized */
+ return userFlags[i];
+}
+
+/* Process rc file
+ * Accepts: file name
+ * .mminit flag
+ * Don't use this feature.
+ */
+
+void dorc (char *file,long flag)
+{
+ int i;
+ char *s,*t,*k,*r,tmp[MAILTMPLEN],tmpx[MAILTMPLEN];
+ extern MAILSTREAM CREATEPROTO;
+ extern MAILSTREAM EMPTYPROTO;
+ DRIVER *d;
+ FILE *f;
+ if ((f = fopen (file ? file : SYSCONFIG,"r")) &&
+ (s = fgets (tmp,MAILTMPLEN,f)) && (t = strchr (s,'\n'))) do {
+ *t++ = '\0'; /* tie off line, find second space */
+ if ((k = strchr (s,' ')) && (k = strchr (++k,' '))) {
+ *k++ = '\0'; /* tie off two words */
+ if (!compare_cstring (s,"set keywords") && !userFlags[0]) {
+ /* yes, get first keyword */
+ k = strtok_r (k,", ",&r);
+ /* copy keyword list */
+ for (i = 0; k && i < NUSERFLAGS; ++i) if (strlen (k) <= MAXUSERFLAG) {
+ if (userFlags[i]) fs_give ((void **) &userFlags[i]);
+ userFlags[i] = cpystr (k);
+ k = strtok_r (NIL,", ",&r);
+ }
+ if (flag) break; /* found "set keywords" in .mminit */
+ }
+
+ else if (!flag) { /* none of these valid in .mminit */
+ if (myUserName) { /* only valid if logged in */
+ if (!compare_cstring (s,"set new-mailbox-format") ||
+ !compare_cstring (s,"set new-folder-format")) {
+ if (!compare_cstring (k,"same-as-inbox")) {
+ if (d = mail_valid (NIL,"INBOX",NIL)) {
+ if (!compare_cstring (d->name,"mbox"))
+ d = (DRIVER *) mail_parameters (NIL,GET_DRIVER,
+ (void *) "unix");
+ else if (!compare_cstring (d->name,"dummy")) d = NIL;
+ }
+ createProto = d ? ((*d->open) (NIL)) : &CREATEPROTO;
+ }
+ else if (!compare_cstring (k,"system-standard"))
+ createProto = &CREATEPROTO;
+ else { /* canonicalize mbox to unix */
+ if (!compare_cstring (k,"mbox")) k = "unix";
+ /* see if a driver name */
+ if (d = (DRIVER *) mail_parameters (NIL,GET_DRIVER,(void *) k))
+ createProto = (*d->open) (NIL);
+ else { /* duh... */
+ sprintf (tmpx,"Unknown new mailbox format in %s: %s",
+ file ? file : SYSCONFIG,k);
+ MM_LOG (tmpx,WARN);
+ }
+ }
+ }
+ if (!compare_cstring (s,"set empty-mailbox-format") ||
+ !compare_cstring (s,"set empty-folder-format")) {
+ if (!compare_cstring (k,"invalid")) appendProto = NIL;
+ else if (!compare_cstring (k,"same-as-inbox"))
+ appendProto = ((d = mail_valid (NIL,"INBOX",NIL)) &&
+ compare_cstring (d->name,"dummy")) ?
+ ((*d->open) (NIL)) : &EMPTYPROTO;
+ else if (!compare_cstring (k,"system-standard"))
+ appendProto = &EMPTYPROTO;
+ else { /* see if a driver name */
+ for (d = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL);
+ d && compare_cstring (d->name,k); d = d->next);
+ if (d) appendProto = (*d->open) (NIL);
+ else { /* duh... */
+ sprintf (tmpx,"Unknown empty mailbox format in %s: %s",
+ file ? file : SYSCONFIG,k);
+ MM_LOG (tmpx,WARN);
+ }
+ }
+ }
+ }
+
+ if (!compare_cstring (s,"set local-host")) {
+ fs_give ((void **) &myLocalHost);
+ myLocalHost = cpystr (k);
+ }
+ else if (!compare_cstring (s,"set news-active-file")) {
+ fs_give ((void **) &newsActive);
+ newsActive = cpystr (k);
+ }
+ else if (!compare_cstring (s,"set news-spool-directory")) {
+ fs_give ((void **) &newsSpool);
+ newsSpool = cpystr (k);
+ }
+ else if (!compare_cstring (s,"set mh-path"))
+ mail_parameters (NIL,SET_MHPATH,(void *) k);
+ else if (!compare_cstring (s,"set mh-allow-inbox"))
+ mail_parameters (NIL,SET_MHALLOWINBOX,(void *) atol (k));
+ else if (!compare_cstring (s,"set news-state-file")) {
+ fs_give ((void **) &myNewsrc);
+ myNewsrc = cpystr (k);
+ }
+ else if (!compare_cstring (s,"set ftp-export-directory")) {
+ fs_give ((void **) &ftpHome);
+ ftpHome = cpystr (k);
+ }
+ else if (!compare_cstring (s,"set public-home-directory")) {
+ fs_give ((void **) &publicHome);
+ publicHome = cpystr (k);
+ }
+ else if (!compare_cstring (s,"set shared-home-directory")) {
+ fs_give ((void **) &sharedHome);
+ sharedHome = cpystr (k);
+ }
+ else if (!compare_cstring (s,"set system-inbox")) {
+ fs_give ((void **) &sysInbox);
+ sysInbox = cpystr (k);
+ }
+ else if (!compare_cstring (s,"set mail-subdirectory")) {
+ fs_give ((void **) &mailsubdir);
+ mailsubdir = cpystr (k);
+ }
+ else if (!compare_cstring (s,"set from-widget"))
+ mail_parameters (NIL,SET_FROMWIDGET,
+ compare_cstring (k,"header-only") ?
+ VOIDT : NIL);
+
+ else if (!compare_cstring (s,"set rsh-command"))
+ mail_parameters (NIL,SET_RSHCOMMAND,(void *) k);
+ else if (!compare_cstring (s,"set rsh-path"))
+ mail_parameters (NIL,SET_RSHPATH,(void *) k);
+ else if (!compare_cstring (s,"set ssh-command"))
+ mail_parameters (NIL,SET_SSHCOMMAND,(void *) k);
+ else if (!compare_cstring (s,"set ssh-path"))
+ mail_parameters (NIL,SET_SSHPATH,(void *) k);
+ else if (!compare_cstring (s,"set tcp-open-timeout"))
+ mail_parameters (NIL,SET_OPENTIMEOUT,(void *) atol (k));
+ else if (!compare_cstring (s,"set tcp-read-timeout"))
+ mail_parameters (NIL,SET_READTIMEOUT,(void *) atol (k));
+ else if (!compare_cstring (s,"set tcp-write-timeout"))
+ mail_parameters (NIL,SET_WRITETIMEOUT,(void *) atol (k));
+ else if (!compare_cstring (s,"set rsh-timeout"))
+ mail_parameters (NIL,SET_RSHTIMEOUT,(void *) atol (k));
+ else if (!compare_cstring (s,"set ssh-timeout"))
+ mail_parameters (NIL,SET_SSHTIMEOUT,(void *) atol (k));
+ else if (!compare_cstring (s,"set maximum-login-trials"))
+ mail_parameters (NIL,SET_MAXLOGINTRIALS,(void *) atol (k));
+ else if (!compare_cstring (s,"set lookahead"))
+ mail_parameters (NIL,SET_LOOKAHEAD,(void *) atol (k));
+ else if (!compare_cstring (s,"set prefetch"))
+ mail_parameters (NIL,SET_PREFETCH,(void *) atol (k));
+ else if (!compare_cstring (s,"set close-on-error"))
+ mail_parameters (NIL,SET_CLOSEONERROR,(void *) atol (k));
+ else if (!compare_cstring (s,"set imap-port"))
+ mail_parameters (NIL,SET_IMAPPORT,(void *) atol (k));
+ else if (!compare_cstring (s,"set pop3-port"))
+ mail_parameters (NIL,SET_POP3PORT,(void *) atol (k));
+ else if (!compare_cstring (s,"set uid-lookahead"))
+ mail_parameters (NIL,SET_UIDLOOKAHEAD,(void *) atol (k));
+ else if (!compare_cstring (s,"set try-ssl-first"))
+ mail_parameters (NIL,SET_TRYSSLFIRST,(void *) atol (k));
+
+ else if (!compare_cstring (s,"set mailbox-protection"))
+ mbx_protection = atol (k);
+ else if (!compare_cstring (s,"set directory-protection"))
+ dir_protection = atol (k);
+ else if (!compare_cstring (s,"set lock-protection"))
+ dotlock_mode = atol (k);
+ else if (!compare_cstring (s,"set ftp-protection"))
+ ftp_protection = atol (k);
+ else if (!compare_cstring (s,"set public-protection"))
+ public_protection = atol (k);
+ else if (!compare_cstring (s,"set shared-protection"))
+ shared_protection = atol (k);
+ else if (!compare_cstring (s,"set ftp-directory-protection"))
+ ftp_dir_protection = atol (k);
+ else if (!compare_cstring (s,"set public-directory-protection"))
+ public_dir_protection = atol (k);
+ else if (!compare_cstring (s,"set shared-directory-protection"))
+ shared_dir_protection = atol (k);
+ else if (!compare_cstring (s,"set dot-lock-file-timeout"))
+ locktimeout = atoi (k);
+ else if (!compare_cstring (s,"set disable-fcntl-locking"))
+ fcntlhangbug = atoi (k);
+ else if (!compare_cstring (s,"set disable-lock-warning"))
+ disableLockWarning = atoi (k);
+ else if (!compare_cstring (s,"set disable-unix-UIDs-and-keywords"))
+ has_no_life = atoi (k);
+ else if (!compare_cstring (s,"set hide-dot-files"))
+ hideDotFiles = atoi (k);
+ else if (!compare_cstring (s,"set list-maximum-level"))
+ list_max_level = atol (k);
+ else if (!compare_cstring (s,"set trust-dns"))
+ mail_parameters (NIL,SET_TRUSTDNS,(void *) atol (k));
+ else if (!compare_cstring (s,"set sasl-uses-ptr-name"))
+ mail_parameters (NIL,SET_SASLUSESPTRNAME,(void *) atol (k));
+ else if (!compare_cstring (s,"set network-filesystem-stat-bug"))
+ netfsstatbug = atoi (k);
+ else if (!compare_cstring (s,"set nntp-range"))
+ mail_parameters (NIL,SET_NNTPRANGE,(void *) atol (k));
+
+ else if (!file) { /* only allowed in system init */
+ if (!compare_cstring (s,"set black-box-directory") &&
+ !blackBoxDir) blackBoxDir = cpystr (k);
+ else if (!compare_cstring(s,"set black-box-default-home-directory")&&
+ blackBoxDir && !blackBoxDefaultHome)
+ blackBoxDefaultHome = cpystr (k);
+ else if (!compare_cstring (s,"set anonymous-home-directory") &&
+ !anonymousHome) anonymousHome = cpystr (k);
+ /* It's tempting to allow setting the CA path
+ * in a user init. However, that opens up a
+ * vector of attack big enough to drive a
+ * truck through... Resist the temptation.
+ */
+ else if (!compare_cstring (s,"set CA-certificate-path"))
+ sslCApath = cpystr (k);
+ else if (!compare_cstring (s,"set disable-plaintext"))
+ disablePlaintext = atoi (k);
+ else if (!compare_cstring (s,"set allowed-login-attempts"))
+ logtry = atoi (k);
+ else if (!compare_cstring (s,"set chroot-server"))
+ closedBox = atoi (k);
+ else if (!compare_cstring (s,"set restrict-mailbox-access"))
+ for (k = strtok_r (k,", ",&r); k; k = strtok_r (NIL,", ",&r)) {
+ if (!compare_cstring (k,"root")) restrictBox |= RESTRICTROOT;
+ else if (!compare_cstring (k,"otherusers"))
+ restrictBox |= RESTRICTOTHERUSER;
+ else if (!compare_cstring (k,"all")) restrictBox = -1;
+ }
+ else if (!compare_cstring (s,"set advertise-the-world"))
+ advertisetheworld = atoi (k);
+ else if (!compare_cstring (s,"set limited-advertise"))
+ limitedadvertise = atoi (k);
+ else if (!compare_cstring
+ (s,"set disable-automatic-shared-namespaces"))
+ noautomaticsharedns = atoi (k);
+ else if (!compare_cstring (s,"set allow-user-config"))
+ allowuserconfig = atoi (k);
+ else if (!compare_cstring (s,"set allow-reverse-dns"))
+ mail_parameters (NIL,SET_ALLOWREVERSEDNS,(void *) atol (k));
+ else if (!compare_cstring (s,"set k5-cp-uses-service-name"))
+ kerb_cp_svr_name = atoi (k);
+ /* must appear in file after any
+ * "set disable-plaintext" command! */
+ else if (!compare_cstring (s,"set plaintext-allowed-clients")) {
+ for (k = strtok_r (k,", ",&r); k && !tcp_isclienthost (k);
+ k = strtok_r (NIL,", ",&r));
+ if (k) disablePlaintext = 0;
+ }
+ }
+ }
+ }
+ } while ((s = fgets (tmp,MAILTMPLEN,f)) && (t = strchr (s,'\n')));
+ if (f) fclose (f); /* flush the file */
+}
+
+/* INBOX create function for tmail/dmail use only
+ * Accepts: mail stream
+ * path name buffer, preloaded with driver-dependent path
+ * Returns: T on success, NIL on failure
+ *
+ * This routine is evil and a truly incredible kludge. It is private for
+ * tmail/dmail and is not supported for any other application.
+ */
+
+long path_create (MAILSTREAM *stream,char *path)
+{
+ long ret;
+ short rsave = restrictBox;
+ restrictBox = NIL; /* can't restrict */
+ if (blackBox) { /* if black box */
+ /* toss out driver dependent names */
+ sprintf (path,"%s/INBOX",mymailboxdir ());
+ blackBox = NIL; /* well that's evil - evil is going on */
+ ret = mail_create (stream,path);
+ blackBox = T; /* restore the box */
+ }
+ /* easy thing otherwise */
+ else ret = mail_create (stream,path);
+ restrictBox = rsave; /* restore restrictions */
+ return ret;
+}
+
+/* Default block notify routine
+ * Accepts: reason for calling
+ * data
+ * Returns: data
+ */
+
+void *mm_blocknotify (int reason,void *data)
+{
+ void *ret = data;
+ switch (reason) {
+ case BLOCK_SENSITIVE: /* entering sensitive code */
+ ret = (void *) (unsigned long) alarm (0);
+ break;
+ case BLOCK_NONSENSITIVE: /* exiting sensitive code */
+ if ((unsigned long) data) alarm ((unsigned long) data);
+ break;
+ default: /* ignore all other reasons */
+ break;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/unix/env_unix.h b/imap/src/osdep/unix/env_unix.h
new file mode 100644
index 00000000..365e5128
--- /dev/null
+++ b/imap/src/osdep/unix/env_unix.h
@@ -0,0 +1,95 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+typedef struct dotlock_base {
+ char lock[MAILTMPLEN];
+ int pipei;
+ int pipeo;
+} DOTLOCK;
+
+
+/* Bits that can be set in restrictBox */
+
+#define RESTRICTROOT 0x1 /* restricted box doesn't allow root */
+#define RESTRICTOTHERUSER 0x2 /* restricted box doesn't allow other user */
+
+/* Subscription definitions for UNIX */
+
+#define SUBSCRIPTIONFILE(t) sprintf (t,"%s/.mailboxlist",myhomedir ())
+#define SUBSCRIPTIONTEMP(t) sprintf (t,"%s/.mlbxlsttmp",myhomedir ())
+
+
+/* dorc() options */
+
+#define SYSCONFIG "/etc/c-client.cf"
+
+
+/* Special users */
+
+#define ANONYMOUSUSER "nobody" /* anonymous user */
+#define UNLOGGEDUSER "root" /* unlogged-in user */
+#define ADMINGROUP "mailadm" /* mail administrator group */
+
+/* Function prototypes */
+
+#include "env.h"
+
+void rfc822_fixed_date (char *date);
+long env_init (char *user,char *home);
+char *myusername_full (unsigned long *flags);
+#define MU_LOGGEDIN 0
+#define MU_NOTLOGGEDIN 1
+#define MU_ANONYMOUS 2
+#define myusername() \
+ myusername_full (NIL)
+char *sysinbox ();
+char *mailboxdir (char *dst,char *dir,char *name);
+long dotlock_lock (char *file,DOTLOCK *base,int fd);
+long dotlock_unlock (DOTLOCK *base);
+int lockname (char *lock,char *fname,int op,long *pid);
+int lockfd (int fd,char *lock,int op);
+int lock_work (char *lock,void *sbuf,int op,long *pid);
+long chk_notsymlink (char *name,void *sbuf);
+void unlockfd (int fd,char *lock);
+long set_mbx_protections (char *mailbox,char *path);
+long get_dir_protection (char *mailbox);
+MAILSTREAM *user_flags (MAILSTREAM *stream);
+char *default_user_flag (unsigned long i);
+void dorc (char *file,long flag);
+long path_create (MAILSTREAM *stream,char *mailbox);
+void grim_pid_reap_status (int pid,int killreq,void *status);
+#define grim_pid_reap(pid,killreq) \
+ grim_pid_reap_status (pid,killreq,NIL)
+long safe_write (int fd,char *buf,long nbytes);
+void *arm_signal (int sig,void *action);
+struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[]);
+long loginpw (struct passwd *pw,int argc,char *argv[]);
+long pw_login (struct passwd *pw,char *auser,char *user,char *home,int argc,
+ char *argv[]);
+void *mm_blocknotify (int reason,void *data);
diff --git a/imap/src/osdep/unix/fdstring.c b/imap/src/osdep/unix/fdstring.c
new file mode 100644
index 00000000..7a491f7d
--- /dev/null
+++ b/imap/src/osdep/unix/fdstring.c
@@ -0,0 +1,99 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: File descriptor string routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 April 1997
+ * Last Edited: 4 April 2007
+ */
+
+#include "mail.h"
+#include "osdep.h"
+#include "misc.h"
+#include "fdstring.h"
+
+/* String driver for fd stringstructs */
+
+static void fd_string_init (STRING *s,void *data,unsigned long size);
+static char fd_string_next (STRING *s);
+static void fd_string_setpos (STRING *s,unsigned long i);
+
+STRINGDRIVER fd_string = {
+ fd_string_init, /* initialize string structure */
+ fd_string_next, /* get next byte in string structure */
+ fd_string_setpos /* set position in string structure */
+};
+
+
+/* Initialize string structure for fd stringstruct
+ * Accepts: string structure
+ * pointer to string
+ * size of string
+ */
+
+static void fd_string_init (STRING *s,void *data,unsigned long size)
+{
+ FDDATA *d = (FDDATA *) data;
+ /* note fd */
+ s->data = (void *) (unsigned long) d->fd;
+ s->data1 = d->pos; /* note file offset */
+ s->size = size; /* note size */
+ s->curpos = s->chunk = d->chunk;
+ s->chunksize = (unsigned long) d->chunksize;
+ s->offset = 0; /* initial position */
+ /* and size of data */
+ s->cursize = min (s->chunksize,size);
+ /* move to that position in the file */
+ lseek (d->fd,d->pos,L_SET);
+ read (d->fd,s->chunk,(size_t) s->cursize);
+}
+
+/* Get next character from fd stringstruct
+ * Accepts: string structure
+ * Returns: character, string structure chunk refreshed
+ */
+
+static char fd_string_next (STRING *s)
+{
+ char c = *s->curpos++; /* get next byte */
+ SETPOS (s,GETPOS (s)); /* move to next chunk */
+ return c; /* return the byte */
+}
+
+
+/* Set string pointer position for fd stringstruct
+ * Accepts: string structure
+ * new position
+ */
+
+static void fd_string_setpos (STRING *s,unsigned long i)
+{
+ if (i > s->size) i = s->size; /* don't permit setting beyond EOF */
+ s->offset = i; /* set new offset */
+ s->curpos = s->chunk; /* reset position */
+ /* set size of data */
+ if (s->cursize = min (s->chunksize,SIZE (s))) {
+ /* move to that position in the file */
+ lseek ((long) s->data,s->data1 + s->offset,L_SET);
+ read ((long) s->data,s->curpos,(size_t) s->cursize);
+ }
+}
diff --git a/imap/src/osdep/unix/fdstring.h b/imap/src/osdep/unix/fdstring.h
new file mode 100644
index 00000000..d0a021bf
--- /dev/null
+++ b/imap/src/osdep/unix/fdstring.h
@@ -0,0 +1,39 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: File descriptor string routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 April 1997
+ * Last Edited: 30 August 2006
+ */
+
+/* Driver-dependent data passed to init method */
+
+typedef struct fd_data {
+ int fd; /* file descriptor */
+ unsigned long pos; /* initial position */
+ char *chunk; /* I/O buffer chunk */
+ unsigned long chunksize; /* I/O buffer chunk length */
+} FDDATA;
+
+
+extern STRINGDRIVER fd_string;
diff --git a/imap/src/osdep/unix/flockcyg.c b/imap/src/osdep/unix/flockcyg.c
new file mode 100644
index 00000000..46281829
--- /dev/null
+++ b/imap/src/osdep/unix/flockcyg.c
@@ -0,0 +1,92 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: flock emulation via fcntl() locking
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 2001
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Cygwin does not seem to have the design flaw in fcntl() locking that
+ * most other systems do (see flocksim.c for details). If some cretin
+ * decides to implement that design flaw, then Cygwin will have to use
+ * flocksim. Also, we don't test NFS either.
+ *
+ * However, Cygwin does have the Windows misfeature (introduced in NT 4.0)
+ * that you can not write to any segment which has a shared lock, and you
+ * can't lock a zero-byte segment either. This screws up the shared-write
+ * mailbox drivers (mbx, mtx, mx, and tenex). As a workaround, we'll only
+ * lock the first byte of the file, meaning that you can't write that byte
+ * shared. It's been suggested to lock the maximum off_t type, but that
+ * risks having a future version of Windows (or Cygwin) deciding that this
+ * also means "no lock".
+ */
+
+#undef flock /* name is used as a struct for fcntl */
+
+/* Emulator for flock() call
+ * Accepts: file descriptor
+ * operation bitmask
+ * Returns: 0 if successful, -1 if failure under BSD conditions
+ */
+
+int flocksim (int fd,int op)
+{
+ char tmp[MAILTMPLEN];
+ int logged = 0;
+ struct flock fl;
+ /* lock one bytes at byte 0 */
+ fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 1;
+ fl.l_pid = getpid (); /* shouldn't be necessary */
+ switch (op & ~LOCK_NB) { /* translate to fcntl() operation */
+ case LOCK_EX: /* exclusive */
+ fl.l_type = F_WRLCK;
+ break;
+ case LOCK_SH: /* shared */
+ fl.l_type = F_RDLCK;
+ break;
+ case LOCK_UN: /* unlock */
+ fl.l_type = F_UNLCK;
+ break;
+ default: /* default */
+ errno = EINVAL;
+ return -1;
+ }
+ while (fcntl (fd,(op & LOCK_NB) ? F_SETLK : F_SETLKW,&fl))
+ if (errno != EINTR) {
+ /* Can't use switch here because these error codes may resolve to the
+ * same value on some systems.
+ */
+ if ((errno != EWOULDBLOCK) && (errno != EAGAIN) && (errno != EACCES)) {
+ sprintf (tmp,"Unexpected file locking failure: %s",strerror (errno));
+ /* give the user a warning of what happened */
+ MM_NOTIFY (NIL,tmp,WARN);
+ if (!logged++) syslog (LOG_ERR,"%s",tmp);
+ if (op & LOCK_NB) return -1;
+ sleep (5); /* slow things down for loops */
+ }
+ /* return failure for non-blocking lock */
+ else if (op & LOCK_NB) return -1;
+ }
+ return 0; /* success */
+}
diff --git a/imap/src/osdep/unix/flockcyg.h b/imap/src/osdep/unix/flockcyg.h
new file mode 100644
index 00000000..0bc53623
--- /dev/null
+++ b/imap/src/osdep/unix/flockcyg.h
@@ -0,0 +1,50 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: flock() emulation via fcntl() locking
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 2001
+ * Last Edited: 30 August 2006
+ */
+
+/* Cygwin does not seem to have the design flaw in fcntl() locking that
+ * most other systems do (see flocksim.c for details). If some cretin
+ * decides to implement that design flaw, then Cygwin will have to use
+ * flocksim. Also, we don't test NFS either
+ */
+
+
+#define flock flocksim /* use ours instead of theirs */
+
+#undef LOCK_SH
+#define LOCK_SH 1 /* shared lock */
+#undef LOCK_EX
+#define LOCK_EX 2 /* exclusive lock */
+#undef LOCK_NB
+#define LOCK_NB 4 /* no blocking */
+#undef LOCK_UN
+#define LOCK_UN 8 /* unlock */
+
+
+/* Function prototypes */
+
+int flocksim (int fd,int operation);
diff --git a/imap/src/osdep/unix/flocklnx.c b/imap/src/osdep/unix/flocklnx.c
new file mode 100644
index 00000000..ca0112ac
--- /dev/null
+++ b/imap/src/osdep/unix/flocklnx.c
@@ -0,0 +1,76 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Safe File Lock for Linux
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 20 April 2005
+ * Last Edited: 30 August 2006
+ */
+
+#undef flock
+
+#include <sys/vfs.h>
+#ifndef NFS_SUPER_MAGIC
+#define NFS_SUPER_MAGIC 0x6969
+#endif
+
+int safe_flock (int fd,int op)
+{
+ struct statfs sfbuf;
+ char tmp[MAILTMPLEN];
+ int e;
+ int logged = 0;
+ /* Check for NFS because Linux 2.6 broke flock() on NFS. Instead of being
+ * a no-op, flock() on NFS now returns ENOLCK. Read
+ * https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=123415
+ * for the gruesome details.
+ */
+ /* check filesystem type */
+ while ((e = fstatfs (fd,&sfbuf)) && (errno == EINTR));
+ if (!e) switch (sfbuf.f_type) {
+ case NFS_SUPER_MAGIC: /* always a fast no-op on NFS */
+ break;
+ default: /* allow on other filesystem types */
+ /* do the lock */
+ while (flock (fd,op)) switch (errno) {
+ case EINTR: /* interrupt */
+ break;
+ case ENOLCK: /* lock table is full */
+ sprintf (tmp,"File locking failure: %s",strerror (errno));
+ mm_log (tmp,WARN); /* give the user a warning of what happened */
+ if (!logged++) syslog (LOG_ERR,tmp);
+ /* return failure if non-blocking lock */
+ if (op & LOCK_NB) return -1;
+ sleep (5); /* slow down in case it loops */
+ break;
+ case EWOULDBLOCK: /* file is locked, LOCK_NB should be set */
+ if (op & LOCK_NB) return -1;
+ case EBADF: /* not valid open file descriptor */
+ case EINVAL: /* invalid operator */
+ default: /* other error code? */
+ sprintf (tmp,"Unexpected file locking failure: %s",strerror (errno));
+ fatal (tmp);
+ }
+ break;
+ }
+ return 0; /* success */
+}
diff --git a/imap/src/osdep/unix/flocksim.c b/imap/src/osdep/unix/flocksim.c
new file mode 100644
index 00000000..82f07837
--- /dev/null
+++ b/imap/src/osdep/unix/flocksim.c
@@ -0,0 +1,920 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: flock emulation via fcntl() locking
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 2001
+ * Last Edited: 11 October 2007
+ */
+
+#undef flock /* name is used as a struct for fcntl */
+
+#ifndef NOFSTATVFS /* thank you, SUN. NOT! */
+# ifndef NOFSTATVFS64
+# ifndef _LARGEFILE64_SOURCE
+# define _LARGEFILE64_SOURCE
+# endif /* _LARGEFILE64_SOURCE */
+# endif /* NOFSTATVFFS64 */
+#include <sys/statvfs.h>
+#endif /* NOFSTATVFS */
+
+#ifndef NSIG /* don't know if this can happen */
+#define NSIG 32 /* a common maximum */
+#endif
+
+/* Emulator for flock() call
+ * Accepts: file descriptor
+ * operation bitmask
+ * Returns: 0 if successful, -1 if failure under BSD conditions
+ */
+
+int flocksim (int fd,int op)
+{
+ char tmp[MAILTMPLEN];
+ int logged = 0;
+ struct stat sbuf;
+ struct ustat usbuf;
+ struct flock fl;
+ /* lock zero bytes at byte 0 */
+ fl.l_whence = SEEK_SET; fl.l_start = fl.l_len = 0;
+ fl.l_pid = getpid (); /* shouldn't be necessary */
+ switch (op & ~LOCK_NB) { /* translate to fcntl() operation */
+ case LOCK_EX: /* exclusive */
+ fl.l_type = F_WRLCK;
+ break;
+ case LOCK_SH: /* shared */
+ fl.l_type = F_RDLCK;
+ break;
+ case LOCK_UN: /* unlock */
+ fl.l_type = F_UNLCK;
+ break;
+ default: /* default */
+ errno = EINVAL;
+ return -1;
+ }
+ /* always return success if disabled */
+ if (mail_parameters (NIL,GET_DISABLEFCNTLLOCK,NIL)) return 0;
+
+ /* Make fcntl() locking of NFS files be a no-op the way it is with flock()
+ * on BSD. This is because the rpc.statd/rpc.lockd daemons don't work very
+ * well and cause cluster-wide hangs if you exercise them at all. The
+ * result of this is that you lose the ability to detect shared mail_open()
+ * on NFS-mounted files. If you are wise, you'll use IMAP instead of NFS
+ * for mail files.
+ *
+ * Sun alleges that it doesn't matter, and that they have fixed all the
+ * rpc.statd/rpc.lockd bugs. As of October 2006, that is still false.
+ *
+ * We need three tests for three major historical variants in SVR4:
+ * 1) In NFSv2, ustat() would return -1 in f_tinode for NFS.
+ * 2) When fstatvfs() was introduced with NFSv3, ustat() was "fixed".
+ * 3) When 64-bit filesystems were introduced, fstatvfs() would return
+ * EOVERFLOW; you have to use fstatvfs64() even though you don't care
+ * about any of the affected values.
+ *
+ * We can't use fstatfs() because fstatfs():
+ * . is documented as being deprecated in SVR4.
+ * . has inconsistent calling conventions (there are two additional int
+ * arguments on Solaris and I don't know what they do).
+ * . returns inconsistent statfs structs. On Solaris, the file system type
+ * is a short called f_fstyp. On AIX, it's an int called f_type that is
+ * documented as always being 0!
+ *
+ * For what it's worth, here's the scoop on fstatfs() elsewhere:
+ *
+ * On Linux, the file system type is a long called f_type that has a file
+ * system type code. A different module (flocklnx.c) uses this because
+ * some knothead "improved" flock() to return ENOLCK on NFS files instead
+ * of being a successful no-op. This "improvement" apparently has been
+ * reverted, but not before it got to many systems in the field.
+ *
+ * On BSD, it's a short called either f_otype or f_type that is documented
+ * as always being zero. Fortunately, BSD has flock() the way it's supposed
+ * to be, and none of this nonsense is necessary.
+ */
+ if (!fstat (fd,&sbuf)) { /* no hope of working if can't fstat()! */
+ /* Any base type that begins with "nfs" or "afs" is considered to be a
+ * network filesystem.
+ */
+#ifndef NOFSTATVFS
+ struct statvfs vsbuf;
+#ifndef NOFSTATVFS64
+ struct statvfs64 vsbuf64;
+ if (!fstatvfs64 (fd,&vsbuf64) && (vsbuf64.f_basetype[1] == 'f') &&
+ (vsbuf64.f_basetype[2] == 's') &&
+ ((vsbuf64.f_basetype[0] == 'n') || (vsbuf64.f_basetype[0] == 'a')))
+ return 0;
+#endif /* NOFSTATVFS64 */
+ if (!fstatvfs (fd,&vsbuf) && (vsbuf.f_basetype[1] == 'f') &&
+ (vsbuf.f_basetype[2] == 's') &&
+ ((vsbuf.f_basetype[0] == 'n') || (vsbuf.f_basetype[0] == 'a')))
+ return 0;
+#endif /* NOFSTATVFS */
+ if (!ustat (sbuf.st_dev,&usbuf) && !++usbuf.f_tinode) return 0;
+ }
+
+ /* do the lock */
+ while (fcntl (fd,(op & LOCK_NB) ? F_SETLK : F_SETLKW,&fl))
+ if (errno != EINTR) {
+ /* Can't use switch here because these error codes may resolve to the
+ * same value on some systems.
+ */
+ if ((errno != EWOULDBLOCK) && (errno != EAGAIN) && (errno != EACCES)) {
+ sprintf (tmp,"Unexpected file locking failure: %.100s",
+ strerror (errno));
+ /* give the user a warning of what happened */
+ MM_NOTIFY (NIL,tmp,WARN);
+ if (!logged++) syslog (LOG_ERR,"%s",tmp);
+ if (op & LOCK_NB) return -1;
+ sleep (5); /* slow things down for loops */
+ }
+ /* return failure for non-blocking lock */
+ else if (op & LOCK_NB) return -1;
+ }
+ return 0; /* success */
+}
+
+/* Master/slave procedures for safe fcntl() locking.
+ *
+ * The purpose of this nonsense is to work around a bad bug in fcntl()
+ * locking. The cretins who designed it decided that a close() should
+ * release any locks made by that process on the file opened on that
+ * file descriptor. Never mind that the lock wasn't made on that file
+ * descriptor, but rather on some other file descriptor.
+ *
+ * This bug is on every implementation of fcntl() locking that I have
+ * tested. Fortunately, on BSD systems, OSF/1, and Linux, we can use the
+ * flock() system call which doesn't have this bug.
+ *
+ * Note that OSF/1, Linux, and some BSD systems have both broken fcntl()
+ * locking and the working flock() locking.
+ *
+ * The program below can be used to demonstrate this problem. Be sure to
+ * let it run long enough for all the sleep() calls to finish.
+ */
+
+#if 0
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/file.h>
+
+main ()
+{
+ struct flock fl;
+ int fd,fd2;
+ char *file = "a.a";
+ if ((fd = creat (file,0666)) < 0)
+ perror ("TEST FAILED: can't create test file"),_exit (errno);
+ close (fd);
+ if (fork ()) { /* parent */
+ if ((fd = open (file,O_RDWR,0)) < 0) abort();
+ /* lock applies to entire file */
+ fl.l_whence = fl.l_start = fl.l_len = 0;
+ fl.l_pid = getpid (); /* shouldn't be necessary */
+ fl.l_type = F_RDLCK;
+ if (fcntl (fd,F_SETLKW,&fl) == -1) abort ();
+ sleep (5);
+ if ((fd2 = open (file,O_RDWR,0)) < 0) abort ();
+ sleep (1);
+ puts ("parent test ready -- will hang here if locking works correctly");
+ close (fd2);
+ wait (0);
+ puts ("OS BUG: child terminated");
+ _exit (0);
+ }
+ else { /* child */
+ sleep (2);
+ if ((fd = open (file,O_RDWR,0666)) < 0) abort ();
+ puts ("child test ready -- child will hang if no bug");
+ /* lock applies to entire file */
+ fl.l_whence = fl.l_start = fl.l_len = 0;
+ fl.l_pid = getpid (); /* shouldn't be necessary */
+ fl.l_type = F_WRLCK;
+ if (fcntl (fd,F_SETLKW,&fl) == -1) abort ();
+ puts ("OS BUG: child got lock");
+ }
+}
+#endif
+
+/* Beware of systems such as AIX which offer flock() as a compatibility
+ * function that is just a jacket into fcntl() locking. The program below
+ * is a variant of the program above, only using flock(). It can be used
+ * to test to see if your system has real flock() or just a jacket into
+ * fcntl().
+ *
+ * Be sure to let it run long enough for all the sleep() calls to finish.
+ * If the program hangs, then flock() works and you can dispense with the
+ * use of this module (you lucky person!).
+ */
+
+#if 0
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/file.h>
+
+main ()
+{
+ int fd,fd2;
+ char *file = "a.a";
+ if ((fd = creat (file,0666)) < 0)
+ perror ("TEST FAILED: can't create test file"),_exit (errno);
+ close (fd);
+ if (fork ()) { /* parent */
+ if ((fd = open (file,O_RDWR,0)) < 0) abort();
+ if (flock (fd,LOCK_SH) == -1) abort ();
+ sleep (5);
+ if ((fd2 = open (file,O_RDWR,0)) < 0) abort ();
+ sleep (1);
+ puts ("parent test ready -- will hang here if flock() works correctly");
+ close (fd2);
+ wait (0);
+ puts ("OS BUG: child terminated");
+ _exit (0);
+ }
+ else { /* child */
+ sleep (2);
+ if ((fd = open (file,O_RDWR,0666)) < 0) abort ();
+ puts ("child test ready -- child will hang if no bug");
+ if (flock (fd,LOCK_EX) == -1) abort ();
+ puts ("OS BUG: child got lock");
+ }
+}
+#endif
+
+/* Master/slave details
+ *
+ * On broken systems, we invoke an inferior fork to execute any driver
+ * dispatches which are likely to tickle this bug; specifically, any
+ * dispatch which may fiddle with a mailbox that is already selected. As
+ * of this writing, these are: delete, rename, status, scan, copy, and append.
+ *
+ * Delete and rename are pretty marginal, yet there are certain clients
+ * (e.g. Outlook Express) that really want to delete or rename the selected
+ * mailbox. The same is true of status, but there are people (such as the
+ * authors of Entourage) who don't understand why status of the selected
+ * mailbox is bad news.
+ *
+ * However, in copy and append it is reasonable to do this to a selected
+ * mailbox. Although scanning the selected mailbox isn't particularly
+ * sensible, it's hard to avoid due to wildcards.
+ *
+ * It is still possible for an application to trigger the bug by doing
+ * mail_open() on the same mailbox twice. Don't do it.
+ *
+ * Once the slave is invoked, the master only has to read events from the
+ * slave's output (see below for these events) and translate these events
+ * to the appropriate c-client callback. When end of file occurs on the pipe,
+ * the master reads the slave's exit status and uses that as the function
+ * return. The append master is slightly more complicated because it has to
+ * send data back to the slave (see below).
+ *
+ * The slave takes callback events from the driver which otherwise would
+ * pass to the main program. Only those events which a slave can actually
+ * encounter are covered here; for example mm_searched() and mm_list() are
+ * not covered since a slave never does the operations that trigger these.
+ * Certain other events (mm_exists(), mm_expunged(), mm_flags()) are discarded
+ * by the slave since the master will generate these events for itself.
+ *
+ * The other events cause the slave to write a newline-terminated string to
+ * its output. The first character of string indicates the event: S for
+ * mm_status(), N for mm_notify(), L for mm_log(), C for mm_critical(), X for
+ * mm_nocritical(), D for mm_diskerror(), F for mm_fatal(), and "A" for append
+ * argument callback. Most of these events also carry data, which carried as
+ * text space-delimited in the string.
+ *
+ * Append argument callback requires the master to provide the slave with
+ * data in the slave's input. The first thing that the master provides is
+ * either a "+" (master has data for the slave) or a "-" (master has no data).
+ * If the master has data, it will then send the flags, internal date, and
+ * message text, each as <text octet count><SPACE><text>.
+ */
+
+/* It should be alright for lockslavep to be a global, since it will always
+ * be zero in the master (which is where threads would be). The slave won't
+ * ever thread, since any driver which threads in its methods probably can't
+ * use fcntl() locking so won't have DR_LOCKING in its driver flags
+ *
+ * lockslavep can not be a static, since it's used by the dispatch macros.
+ */
+
+int lockslavep = 0; /* non-zero means slave process for locking */
+static int lockproxycopy = 0; /* non-zero means redo copy as proxy */
+FILE *slavein = NIL; /* slave input */
+FILE *slaveout = NIL; /* slave output */
+
+
+/* Common master
+ * Accepts: permitted stream
+ * append callback (append calls only, else NIL)
+ * data for callback (append calls only, else NIL)
+ * Returns: (master) T if slave succeeded, NIL if slave failed
+ * (slave) NIL always, with lockslavep non-NIL
+ */
+
+static long master (MAILSTREAM *stream,append_t af,void *data)
+{
+ MAILSTREAM *st;
+ MAILSTATUS status;
+ STRING *message;
+ FILE *pi,*po;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ long ret = NIL;
+ unsigned long i,j;
+ int c,pid,pipei[2],pipeo[2];
+ char *s,*t,event[MAILTMPLEN],tmp[MAILTMPLEN];
+ lockproxycopy = NIL; /* not doing a lock proxycopy */
+ /* make pipe from slave */
+ if (pipe (pipei) < 0) mm_log ("Can't create input pipe",ERROR);
+ else if (pipe (pipeo) < 0) {
+ mm_log ("Can't create output pipe",ERROR);
+ close (pipei[0]); close (pipei[1]);
+ }
+ else if ((pid = fork ()) < 0) {/* make slave */
+ mm_log ("Can't create execution process",ERROR);
+ close (pipei[0]); close (pipei[1]);
+ close (pipeo[0]); close (pipeo[1]);
+ }
+ else if (lockslavep = !pid) { /* are we slave or master? */
+ alarm (0); /* slave doesn't have alarms or signals */
+ for (c = 0; c < NSIG; c++) signal (c,SIG_DFL);
+ if (!(slavein = fdopen (pipeo[0],"r")) ||
+ !(slaveout = fdopen (pipei[1],"w")))
+ fatal ("Can't do slave pipe buffered I/O");
+ close (pipei[0]); /* close parent's side of the pipes */
+ close (pipeo[1]);
+ }
+
+ else { /* master process */
+ void *blockdata = (*bn) (BLOCK_SENSITIVE,NIL);
+ close (pipei[1]); /* close slave's side of the pipes */
+ close (pipeo[0]);
+ if (!(pi = fdopen (pipei[0],"r")) || !(po = fdopen (pipeo[1],"w")))
+ fatal ("Can't do master pipe buffered I/O");
+ /* do slave events until EOF */
+ /* read event */
+ while (fgets (event,MAILTMPLEN-1,pi)) {
+ if (!(s = strchr (event,'\n'))) {
+ sprintf (tmp,"Execution process event string too long: %.500s",event);
+ fatal (tmp);
+ }
+ *s = '\0'; /* tie off event at end of line */
+ switch (event[0]) { /* analyze event */
+ case 'A': /* append callback */
+ if ((*af) (NIL,data,&s,&t,&message)) {
+ if (i = message ? SIZE (message) : 0) {
+ if (!s) s = ""; /* default values */
+ if (!t) t = "";
+ }
+ else s = t = ""; /* no flags or date if no message */
+ errno = NIL; /* reset last error */
+ /* build response */
+ if (fprintf (po,"+%lu %s%lu %s%lu ",strlen (s),s,strlen (t),t,i) < 0)
+ fatal ("Failed to pipe append command");
+ /* write message text */
+ if (i) do if (putc (c = 0xff & SNX (message),po) == EOF) {
+ sprintf (tmp,"Failed to pipe %lu bytes (of %lu), last=%u: %.100s",
+ i,message->size,c,strerror (errno));
+ fatal (tmp);
+ } while (--i);
+ }
+ else putc ('-',po); /* append error */
+ fflush (po);
+ break;
+ case '&': /* slave wants a proxycopy? */
+ lockproxycopy = T;
+ break;
+
+ case 'L': /* mm_log() */
+ i = strtoul (event+1,&s,10);
+ if (!s || (*s++ != ' ')) {
+ sprintf (tmp,"Invalid log event arguments: %.500s",event);
+ fatal (tmp);
+ }
+ mm_log (s,i);
+ break;
+ case 'N': /* mm_notify() */
+ st = (MAILSTREAM *) strtoul (event+1,&s,16);
+ if (s && (*s++ == ' ')) {
+ i = strtoul (s,&s,10);/* get severity */
+ if (s && (*s++ == ' ')) {
+ mm_notify ((st == stream) ? stream : NIL,s,i);
+ break;
+ }
+ }
+ sprintf (tmp,"Invalid notify event arguments: %.500s",event);
+ fatal (tmp);
+
+ case 'S': /* mm_status() */
+ st = (MAILSTREAM *) strtoul (event+1,&s,16);
+ if (s && (*s++ == ' ')) {
+ status.flags = strtoul (s,&s,10);
+ if (s && (*s++ == ' ')) {
+ status.messages = strtoul (s,&s,10);
+ if (s && (*s++ == ' ')) {
+ status.recent = strtoul (s,&s,10);
+ if (s && (*s++ == ' ')) {
+ status.unseen = strtoul (s,&s,10);
+ if (s && (*s++ == ' ')) {
+ status.uidnext = strtoul (s,&s,10);
+ if (s && (*s++ == ' ')) {
+ status.uidvalidity = strtoul (s,&s,10);
+ if (s && (*s++ == ' ')) {
+ mm_status ((st == stream) ? stream : NIL,s,&status);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ sprintf (tmp,"Invalid status event arguments: %.500s",event);
+ fatal (tmp);
+ case 'C': /* mm_critical() */
+ st = (MAILSTREAM *) strtoul (event+1,&s,16);
+ mm_critical ((st == stream) ? stream : NIL);
+ break;
+ case 'X': /* mm_nocritical() */
+ st = (MAILSTREAM *) strtoul (event+1,&s,16);
+ mm_nocritical ((st == stream) ? stream : NIL);
+ break;
+
+ case 'D': /* mm_diskerror() */
+ st = (MAILSTREAM *) strtoul (event+1,&s,16);
+ if (s && (*s++ == ' ')) {
+ i = strtoul (s,&s,10);
+ if (s && (*s++ == ' ')) {
+ j = (long) strtoul (s,NIL,10);
+ if (st == stream) /* let's hope it's on usable stream */
+ putc (mm_diskerror (stream,(long) i,j) ? '+' : '-',po);
+ else if (j) { /* serious diskerror on slave-created stream */
+ mm_log ("Retrying disk write to avoid mailbox corruption!",WARN);
+ sleep (5); /* give some time for it to clear up */
+ putc ('-',po); /* don't abort */
+ }
+ else { /* recoverable on slave-created stream */
+ mm_log ("Error on disk write",ERROR);
+ putc ('+',po); /* so abort it */
+ }
+ fflush (po); /* force it out either way */
+ break;
+ }
+ }
+ sprintf (tmp,"Invalid diskerror event arguments: %.500s",event);
+ fatal (tmp);
+ case 'F': /* mm_fatal() */
+ mm_fatal (event+1);
+ break;
+ default: /* random lossage */
+ sprintf (tmp,"Unknown event from execution process: %.500s",event);
+ fatal (tmp);
+ }
+ }
+ fclose (pi); fclose (po); /* done with the pipes */
+ /* get slave status */
+ grim_pid_reap_status (pid,NIL,&ret);
+ if (ret & 0177) { /* signal or stopped */
+ sprintf (tmp,"Execution process terminated abnormally (%lx)",ret);
+ mm_log (tmp,ERROR);
+ ret = NIL;
+ }
+ else ret >>= 8; /* return exit code */
+ (*bn) (BLOCK_NONSENSITIVE,blockdata);
+ }
+ return ret; /* return status */
+}
+
+/* Safe driver calls */
+
+
+/* Safely delete mailbox
+ * Accepts: driver to call under slave
+ * MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long safe_delete (DRIVER *dtb,MAILSTREAM *stream,char *mbx)
+{
+ long ret = master (stream,NIL,NIL);
+ if (lockslavep) exit ((*dtb->mbxdel) (stream,mbx));
+ return ret;
+}
+
+
+/* Safely rename mailbox
+ * Accepts: driver to call under slave
+ * MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long safe_rename (DRIVER *dtb,MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = master (stream,NIL,NIL);
+ if (lockslavep) exit ((*dtb->mbxren) (stream,old,newname));
+ return ret;
+}
+
+
+/* Safely get status of mailbox
+ * Accepts: driver to call under slave
+ * MAIL stream
+ * mailbox name
+ * status flags
+ * Returns: T on success, NIL on failure
+ */
+
+long safe_status (DRIVER *dtb,MAILSTREAM *stream,char *mbx,long flags)
+{
+ long ret = master (stream,NIL,NIL);
+ if (lockslavep) exit ((*dtb->status) (stream,mbx,flags));
+ return ret;
+}
+
+
+/* Scan file for contents
+ * Accepts: driver to call under slave
+ * file name
+ * desired contents
+ * length of contents
+ * length of file
+ * Returns: NIL if contents not found, T if found
+ */
+
+long safe_scan_contents (DRIVER *dtb,char *name,char *contents,
+ unsigned long csiz,unsigned long fsiz)
+{
+ long ret = master (NIL,NIL,NIL);
+ if (lockslavep) exit (scan_contents (dtb,name,contents,csiz,fsiz));
+ return ret;
+}
+
+/* Safely copy message to mailbox
+ * Accepts: driver to call under slave
+ * MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long safe_copy (DRIVER *dtb,MAILSTREAM *stream,char *seq,char *mbx,long flags)
+{
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ long ret = master (stream,NIL,NIL);
+ if (lockslavep) {
+ /* don't do proxycopy in slave */
+ if (pc) mail_parameters (stream,SET_MAILPROXYCOPY,(void *) slaveproxycopy);
+ exit ((*dtb->copy) (stream,seq,mbx,flags));
+ }
+ /* do any proxycopy in master */
+ if (lockproxycopy && pc) return (*pc) (stream,seq,mbx,flags);
+ return ret;
+}
+
+
+/* Append package for slave */
+
+typedef struct append_data {
+ int first; /* flag indicating first message */
+ char *flags; /* message flags */
+ char *date; /* message date */
+ char *msg; /* message text */
+ STRING message; /* message stringstruct */
+} APPENDDATA;
+
+
+/* Safely append message to mailbox
+ * Accepts: driver to call under slave
+ * MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long safe_append (DRIVER *dtb,MAILSTREAM *stream,char *mbx,append_t af,
+ void *data)
+{
+ long ret = master (stream,af,data);
+ if (lockslavep) {
+ APPENDDATA ad;
+ ad.first = T; /* initialize initial append package */
+ ad.flags = ad.date = ad.msg = NIL;
+ exit ((*dtb->append) (stream,mbx,slave_append,&ad));
+ }
+ return ret;
+}
+
+/* Slave callbacks */
+
+
+/* Message exists (i.e. there are that many messages in the mailbox)
+ * Accepts: MAIL stream
+ * message number
+ */
+
+void slave_exists (MAILSTREAM *stream,unsigned long number)
+{
+ /* this event never passed by slaves */
+}
+
+
+/* Message expunged
+ * Accepts: MAIL stream
+ * message number
+ */
+
+void slave_expunged (MAILSTREAM *stream,unsigned long number)
+{
+ /* this event never passed by slaves */
+}
+
+
+/* Message status changed
+ * Accepts: MAIL stream
+ * message number
+ */
+
+void slave_flags (MAILSTREAM *stream,unsigned long number)
+{
+ /* this event never passed by slaves */
+}
+
+/* Mailbox status
+ * Accepts: MAIL stream
+ * mailbox name
+ * mailbox status
+ */
+
+void slave_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status)
+{
+ int i,c;
+ fprintf (slaveout,"S%lx %lu %lu %lu %lu %lu %lu ",
+ (unsigned long) stream,status->flags,status->messages,status->recent,
+ status->unseen,status->uidnext,status->uidvalidity,mailbox);
+ /* yow! are we paranoid enough yet? */
+ for (i = 0; (i < 500) && (c = *mailbox++); ++i) switch (c) {
+ case '\r': case '\n': /* newline in a mailbox name? */
+ c = ' ';
+ default:
+ putc (c,slaveout);
+ }
+ putc ('\n',slaveout);
+ fflush (slaveout);
+}
+
+/* Notification event
+ * Accepts: MAIL stream
+ * string to log
+ * error flag
+ */
+
+void slave_notify (MAILSTREAM *stream,char *string,long errflg)
+{
+ int i,c;
+ fprintf (slaveout,"N%lx %lu ",(unsigned long) stream,errflg);
+ /* prevent more than 500 bytes */
+ for (i = 0; (i < 500) && (c = *string++); ++i) switch (c) {
+ case '\r': case '\n': /* or embedded newline */
+ c = ' ';
+ default:
+ putc (c,slaveout);
+ }
+ putc ('\n',slaveout);
+ fflush (slaveout);
+}
+
+
+/* Log an event for the user to see
+ * Accepts: string to log
+ * error flag
+ */
+
+void slave_log (char *string,long errflg)
+{
+ int i,c;
+ fprintf (slaveout,"L%lu ",errflg);
+ /* prevent more than 500 bytes */
+ for (i = 0; (i < 500) && (c = *string++); ++i) switch (c) {
+ case '\r': case '\n': /* or embedded newline */
+ c = ' ';
+ default:
+ putc (c,slaveout);
+ }
+ putc ('\n',slaveout);
+ fflush (slaveout);
+}
+
+/* About to enter critical code
+ * Accepts: stream
+ */
+
+void slave_critical (MAILSTREAM *stream)
+{
+ fprintf (slaveout,"C%lx\n",(unsigned long) stream);
+ fflush (slaveout);
+}
+
+
+/* About to exit critical code
+ * Accepts: stream
+ */
+
+void slave_nocritical (MAILSTREAM *stream)
+{
+ fprintf (slaveout,"X%lx\n",(unsigned long) stream);
+ fflush (slaveout);
+}
+
+/* Disk error found
+ * Accepts: stream
+ * system error code
+ * flag indicating that mailbox may be clobbered
+ * Returns: abort flag
+ */
+
+long slave_diskerror (MAILSTREAM *stream,long errcode,long serious)
+{
+ char tmp[MAILTMPLEN];
+ int c;
+ long ret = NIL;
+ fprintf (slaveout,"D%lx %lu %lu\n",(unsigned long) stream,errcode,serious);
+ fflush (slaveout);
+ switch (c = getc (slavein)) {
+ case EOF: /* pipe broken */
+ slave_fatal ("Pipe broken reading diskerror response");
+ case '+': /* user wants to abort */
+ ret = LONGT;
+ case '-': /* no abort */
+ break;
+ default:
+ sprintf (tmp,"Unknown master response for diskerror: %c",c);
+ slave_fatal (tmp);
+ }
+ return ret;
+}
+
+
+/* Log a fatal error event
+ * Accepts: string to log
+ * Does not return
+ */
+
+void slave_fatal (char *string)
+{
+ int i,c;
+ syslog (LOG_ALERT,"IMAP toolkit slave process crash: %.500s",string);
+ putc ('F',slaveout);
+ /* prevent more than 500 bytes */
+ for (i = 0; (i < 500) && (c = *string++); ++i) switch (c) {
+ case '\r': case '\n': /* newline in a mailbox name? */
+ c = ' ';
+ default:
+ putc (c,slaveout);
+ }
+ putc ('\n',slaveout);
+ fflush (slaveout);
+ abort (); /* die */
+}
+
+/* Append read buffer
+ * Accepts: number of bytes to read
+ * error message if fails
+ * Returns: read-in string
+ */
+
+static char *slave_append_read (unsigned long n,char *error)
+{
+#if 0
+ unsigned long i;
+#endif
+ int c;
+ char *t,tmp[MAILTMPLEN];
+ char *s = (char *) fs_get (n + 1);
+ s[n] = '\0';
+#if 0
+ /* This doesn't work on Solaris with GCC. I think that it's a C library
+ * bug, since the problem only shows up if the application does fread()
+ * on some other file
+ */
+ for (t = s; n && ((i = fread (t,1,n,slavein)); t += i,n -= i);
+#else
+ for (t = s; n && ((c = getc (slavein)) != EOF); *t++ = c,--n);
+#endif
+ if (n) {
+ sprintf(tmp,"Pipe broken reading %.100s with %lu bytes remaining",error,n);
+ slave_fatal (tmp);
+ }
+ return s;
+}
+
+/* Append message callback
+ * Accepts: MAIL stream
+ * append data package
+ * pointer to return initial flags
+ * pointer to return message internal date
+ * pointer to return stringstruct of message or NIL to stop
+ * Returns: T if success (have message or stop), NIL if error
+ */
+
+long slave_append (MAILSTREAM *stream,void *data,char **flags,char **date,
+ STRING **message)
+{
+ char tmp[MAILTMPLEN];
+ unsigned long n;
+ int c;
+ APPENDDATA *ad = (APPENDDATA *) data;
+ /* flush text of previous message */
+ if (ad->flags) fs_give ((void **) &ad->flags);
+ if (ad->date) fs_give ((void **) &ad->date);
+ if (ad->msg) fs_give ((void **) &ad->msg);
+ *flags = *date = NIL; /* assume no flags or date */
+ fputs ("A\n",slaveout); /* tell master we're doing append callback */
+ fflush (slaveout);
+ switch (c = getc (slavein)) { /* what did master say? */
+ case '+': /* have message, get size of flags */
+ for (n = 0; isdigit (c = getc (slavein)); n *= 10, n += (c - '0'));
+ if (c != ' ') {
+ if (c == EOF) sprintf (tmp,"Pipe broken after flag size %lu",n);
+ sprintf (tmp,"Missing delimiter after flag size %lu: %c",n,c);
+ slave_fatal (tmp);
+ }
+ if (n) *flags = ad->flags = slave_append_read (n,"flags");
+ /* get size of date */
+ for (n = 0; isdigit (c = getc (slavein)); n *= 10, n += (c - '0'));
+ if (c != ' ') {
+ if (c == EOF) sprintf (tmp,"Pipe broken after date size %lu",n);
+ else sprintf (tmp,"Missing delimiter after date size %lu: %c",n,c);
+ slave_fatal (tmp);
+ }
+ if (n) *date = ad->date = slave_append_read (n,"date");
+ /* get size of message */
+ for (n = 0; isdigit (c = getc (slavein)); n *= 10, n += (c - '0'));
+ if (c != ' ') {
+ if (c == EOF) sprintf (tmp,"Pipe broken after message size %lu",n);
+ sprintf (tmp,"Missing delimiter after message size %lu: %c",n,c);
+ slave_fatal (tmp);
+ }
+ if (n) { /* make buffer for message */
+ ad->msg = slave_append_read (n,"message");
+ /* initialize stringstruct */
+ INIT (&ad->message,mail_string,(void *) ad->msg,n);
+ ad->first = NIL; /* no longer first message */
+ *message = &ad->message; /* return message */
+ }
+ else *message = NIL; /* empty message */
+ return LONGT;
+ case '-': /* error */
+ *message = NIL; /* set stop */
+ break;
+ case EOF: /* end of file */
+ slave_fatal ("Pipe broken reading append response");
+ default: /* unknown event */
+ sprintf (tmp,"Unknown master response for append: %c",c);
+ slave_fatal (tmp);
+ }
+ return NIL; /* return failure */
+}
+
+/* Proxy copy across mailbox formats
+ * Accepts: mail stream
+ * sequence to copy on this stream
+ * destination mailbox
+ * option flags
+ * Returns: T if success, else NIL
+ */
+
+long slaveproxycopy (MAILSTREAM *stream,char *sequence,char *mailbox,
+ long options)
+{
+ fputs ("&\n",slaveout); /* redo copy as append */
+ fflush (slaveout);
+ return NIL; /* failure for now */
+}
diff --git a/imap/src/osdep/unix/flocksim.h b/imap/src/osdep/unix/flocksim.h
new file mode 100644
index 00000000..719eba46
--- /dev/null
+++ b/imap/src/osdep/unix/flocksim.h
@@ -0,0 +1,117 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: flock() emulation via fcntl() locking
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 2001
+ * Last Edited: 20 December 2006
+ */
+
+
+#include "dummy.h" /* get scan_contents() prototype */
+
+#define flock flocksim /* use ours instead of theirs */
+
+#undef LOCK_SH
+#define LOCK_SH 1 /* shared lock */
+#undef LOCK_EX
+#define LOCK_EX 2 /* exclusive lock */
+#undef LOCK_NB
+#define LOCK_NB 4 /* no blocking */
+#undef LOCK_UN
+#define LOCK_UN 8 /* unlock */
+
+/* Safe locking definitions */
+
+extern int lockslavep; /* non-zero means slave process */
+
+#undef SAFE_DELETE
+#define SAFE_DELETE(dtb,stream,mbx) (dtb->flags & DR_LOCKING) ? \
+ safe_delete (dtb,stream,mbx) : (*dtb->mbxdel) (stream,mbx)
+#undef SAFE_RENAME
+#define SAFE_RENAME(dtb,stream,old,newname) (dtb->flags & DR_LOCKING) ? \
+ safe_rename (dtb,stream,old,newname) : (*dtb->mbxren) (stream,old,newname)
+#undef SAFE_STATUS
+#define SAFE_STATUS(dtb,stream,mbx,bits) (dtb->flags & DR_LOCKING) ? \
+ safe_status (dtb,stream,mbx,bits) : (*dtb->status) (stream,mbx,bits)
+#undef SAFE_SCAN_CONTENTS
+#define SAFE_SCAN_CONTENTS(dtb,name,contents,csiz,fsiz) \
+ (!dtb || (dtb->flags & DR_LOCKING)) ? \
+ safe_scan_contents (dtb,name,contents,csiz,fsiz) : \
+ scan_contents (dtb,name,contents,csiz,fsiz)
+#undef SAFE_COPY
+#define SAFE_COPY(dtb,stream,seq,mbx,bits) (dtb->flags & DR_LOCKING) ? \
+ safe_copy (dtb,stream,seq,mbx,bits) : (*dtb->copy) (stream,seq,mbx,bits)
+#undef SAFE_APPEND
+#define SAFE_APPEND(dtb,stream,mbx,af,data) (dtb->flags & DR_LOCKING) ? \
+ safe_append (dtb,stream,mbx,af,data) : (*dtb->append) (stream,mbx,af,data)
+
+#undef MM_EXISTS
+#define MM_EXISTS (lockslavep ? slave_exists : mm_exists)
+#undef MM_EXPUNGED
+#define MM_EXPUNGED (lockslavep ? slave_expunged : mm_expunged)
+#undef MM_FLAGS
+#define MM_FLAGS (lockslavep ? slave_flags : mm_flags)
+#undef MM_NOTIFY
+#define MM_NOTIFY (lockslavep ? slave_notify : mm_notify)
+#undef MM_STATUS
+#define MM_STATUS (lockslavep ? slave_status : mm_status)
+#undef MM_LOG
+#define MM_LOG (lockslavep ? slave_log : mm_log)
+#undef MM_CRITICAL
+#define MM_CRITICAL (lockslavep ? slave_critical : mm_critical)
+#undef MM_NOCRITICAL
+#define MM_NOCRITICAL (lockslavep ? slave_nocritical : mm_nocritical)
+#undef MM_DISKERROR
+#define MM_DISKERROR (lockslavep ? slave_diskerror : mm_diskerror)
+#undef MM_FATAL
+#define MM_FATAL (lockslavep ? slave_fatal : mm_fatal)
+#undef MM_APPEND
+#define MM_APPEND(af) (lockslavep ? slave_append : (*af))
+
+/* Function prototypes */
+
+int flocksim (int fd,int operation);
+
+long safe_delete (DRIVER *dtb,MAILSTREAM *stream,char *mbx);
+long safe_rename (DRIVER *dtb,MAILSTREAM *stream,char *old,char *newname);
+long safe_status (DRIVER *dtb,MAILSTREAM *stream,char *mbx,long flags);
+long safe_scan_contents (DRIVER *dtb,char *name,char *contents,
+ unsigned long csiz,unsigned long fsiz);
+long safe_copy (DRIVER *dtb,MAILSTREAM *stream,char *seq,char *mbx,long flags);
+long safe_append (DRIVER *dtb,MAILSTREAM *stream,char *mbx,append_t af,
+ void *data);
+
+void slave_exists (MAILSTREAM *stream,unsigned long number);
+void slave_expunged (MAILSTREAM *stream,unsigned long number);
+void slave_flags (MAILSTREAM *stream,unsigned long number);
+void slave_notify (MAILSTREAM *stream,char *string,long errflg);
+void slave_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status);
+void slave_log (char *string,long errflg);
+void slave_critical (MAILSTREAM *stream);
+void slave_nocritical (MAILSTREAM *stream);
+long slave_diskerror (MAILSTREAM *stream,long errcode,long serious);
+void slave_fatal (char *string);
+long slave_append (MAILSTREAM *stream,void *data,char **flags,char **date,
+ STRING **message);
+long slaveproxycopy (MAILSTREAM *stream,char *sequence,char *mailbox,
+ long options);
diff --git a/imap/src/osdep/unix/fs_unix.c b/imap/src/osdep/unix/fs_unix.c
new file mode 100644
index 00000000..b433e1b4
--- /dev/null
+++ b/imap/src/osdep/unix/fs_unix.c
@@ -0,0 +1,71 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Free storage management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Get a block of free storage
+ * Accepts: size of desired block
+ * Returns: free storage block
+ */
+
+void *fs_get (size_t size)
+{
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ void *data = (*bn) (BLOCK_SENSITIVE,NIL);
+ void *block = malloc (size ? size : (size_t) 1);
+ if (!block) fatal ("Out of memory");
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ return (block);
+}
+
+
+/* Resize a block of free storage
+ * Accepts: ** pointer to current block
+ * new size
+ */
+
+void fs_resize (void **block,size_t size)
+{
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ void *data = (*bn) (BLOCK_SENSITIVE,NIL);
+ if (!(*block = realloc (*block,size ? size : (size_t) 1)))
+ fatal ("Can't resize memory");
+ (*bn) (BLOCK_NONSENSITIVE,data);
+}
+
+
+/* Return a block of free storage
+ * Accepts: ** pointer to free storage block
+ */
+
+void fs_give (void **block)
+{
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ void *data = (*bn) (BLOCK_SENSITIVE,NIL);
+ free (*block);
+ *block = NIL;
+ (*bn) (BLOCK_NONSENSITIVE,data);
+}
diff --git a/imap/src/osdep/unix/fsync.c b/imap/src/osdep/unix/fsync.c
new file mode 100644
index 00000000..7a63b0f8
--- /dev/null
+++ b/imap/src/osdep/unix/fsync.c
@@ -0,0 +1,39 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: File sync emulator
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 3 May 1995
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Emulator for BSD fsync() call
+ * Accepts: file name
+ * Returns: 0 if successful, -1 if failure
+ */
+
+int fsync (int fd)
+{
+ sync ();
+ return 0;
+}
diff --git a/imap/src/osdep/unix/ftl_unix.c b/imap/src/osdep/unix/ftl_unix.c
new file mode 100644
index 00000000..e927360d
--- /dev/null
+++ b/imap/src/osdep/unix/ftl_unix.c
@@ -0,0 +1,39 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX crash management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Report a fatal error
+ * Accepts: string to output
+ */
+
+void fatal (char *string)
+{
+ MM_FATAL (string); /* pass up the string */
+ syslog (LOG_ALERT,"IMAP toolkit crash: %.100s",string);
+ abort (); /* die horribly */
+}
diff --git a/imap/src/osdep/unix/gethstid.c b/imap/src/osdep/unix/gethstid.c
new file mode 100644
index 00000000..01516976
--- /dev/null
+++ b/imap/src/osdep/unix/gethstid.c
@@ -0,0 +1,38 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Get host ID emulator
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 3 May 1995
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Emulator for BSD gethostid() call
+ * Returns: unique identifier for this machine
+ */
+
+long gethostid (void)
+{
+ /* No gethostid() here, so just fake it and hope things turn out okay. */
+ return 0xdeadface;
+}
diff --git a/imap/src/osdep/unix/getspnam.c b/imap/src/osdep/unix/getspnam.c
new file mode 100644
index 00000000..cc01daf8
--- /dev/null
+++ b/imap/src/osdep/unix/getspnam.c
@@ -0,0 +1,65 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: 64-bit getsockname()/getpeername() emulator
+ *
+ * Author: Mark Crispin from code contributed by Chris Ross
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 5 November 2004
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Jacket into getpeername()
+ * Accepts: socket
+ * pointer to socket address
+ * void pointer to len
+ * Returns: 0 if success, -1 if error
+ */
+
+int Getpeername (int s,struct sockaddr *name,size_t *namelen)
+{
+ int ret;
+ socklen_t len = (socklen_t) *namelen;
+ ret = getpeername (s,name,&len);
+ *namelen = (size_t) len;
+ return ret;
+}
+
+
+/* Jacket into getsockname()
+ * Accepts: socket
+ * pointer to socket address
+ * void pointer to len
+ * Returns: 0 if success, -1 if error
+ */
+
+int Getsockname (int s,struct sockaddr *name,size_t *namelen)
+{
+ int ret;
+ socklen_t len = (socklen_t) *namelen;
+ ret = getsockname (s,name,&len);
+ *namelen = (size_t) len;
+ return ret;
+}
+
+
+#define getpeername Getpeername
+#define getsockname Getsockname
diff --git a/imap/src/osdep/unix/gr_wait.c b/imap/src/osdep/unix/gr_wait.c
new file mode 100644
index 00000000..626c57e8
--- /dev/null
+++ b/imap/src/osdep/unix/gr_wait.c
@@ -0,0 +1,46 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX Grim PID Reaper -- wait() version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 30 November 1993
+ * Last Edited: 30 August 2006
+ */
+
+/* Grim PID reaper
+ * Accepts: process ID
+ * kill request flag
+ * status return value
+ */
+
+void grim_pid_reap_status (int pid,int killreq,void *status)
+{
+ int r;
+ if (killreq) {
+ kill (pid,SIGHUP); /* kill if not already dead */
+ alarm (10); /* in case we get hosed */
+ while (((r = wait (NIL)) != pid) &&
+ ((r > 0) || ((errno != ECHILD) && (errno != EINTR))));
+ alarm (0); /* cancel the alarm */
+ }
+ else while (((r = wait (status)) != pid) && ((r > 0) || (errno != ECHILD)));
+}
diff --git a/imap/src/osdep/unix/gr_wait4.c b/imap/src/osdep/unix/gr_wait4.c
new file mode 100644
index 00000000..f394b84c
--- /dev/null
+++ b/imap/src/osdep/unix/gr_wait4.c
@@ -0,0 +1,39 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX Grim PID Reaper -- wait4() version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 30 November 1993
+ * Last Edited: 30 August 2006
+ */
+
+/* Grim PID reaper
+ * Accepts: process ID
+ * kill request flag
+ * status return value
+ */
+
+void grim_pid_reap_status (int pid,int killreq,void *status)
+{
+ if (killreq) kill(pid,SIGHUP);/* kill if not already dead */
+ while ((wait4 (pid,status,NIL,NIL) < 0) && (errno != ECHILD));
+}
diff --git a/imap/src/osdep/unix/gr_waitp.c b/imap/src/osdep/unix/gr_waitp.c
new file mode 100644
index 00000000..8b230f9d
--- /dev/null
+++ b/imap/src/osdep/unix/gr_waitp.c
@@ -0,0 +1,39 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX Grim PID Reaper -- waitpid() version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 30 November 1993
+ * Last Edited: 30 August 2006
+ */
+
+/* Grim PID reaper
+ * Accepts: process ID
+ * kill request flag
+ * status return value
+ */
+
+void grim_pid_reap_status (int pid,int killreq,void *status)
+{
+ if (killreq) kill(pid,SIGHUP);/* kill if not already dead */
+ while ((waitpid (pid,status,NIL) < 0) && (errno != ECHILD));
+}
diff --git a/imap/src/osdep/unix/ip4_unix.c b/imap/src/osdep/unix/ip4_unix.c
new file mode 100644
index 00000000..23d399e1
--- /dev/null
+++ b/imap/src/osdep/unix/ip4_unix.c
@@ -0,0 +1,184 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX IPv4 routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 18 December 2003
+ * Last Edited: 30 August 2006
+ */
+
+#define SADRLEN sizeof (struct sockaddr)
+
+#define SADR4(sadr) ((struct sockaddr_in *) sadr)
+#define SADR4LEN sizeof (struct sockaddr_in)
+#define SADR4ADR(sadr) SADR4 (sadr)->sin_addr
+#define ADR4LEN sizeof (struct in_addr)
+#define SADR4PORT(sadr) SADR4 (sadr)->sin_port
+
+
+/* IP abstraction layer */
+
+char *ip_sockaddrtostring (struct sockaddr *sadr);
+long ip_sockaddrtoport (struct sockaddr *sadr);
+void *ip_stringtoaddr (char *text,size_t *len,int *family);
+struct sockaddr *ip_newsockaddr (size_t *len);
+struct sockaddr *ip_sockaddr (int family,void *adr,size_t adrlen,
+ unsigned short port,size_t *len);
+char *ip_sockaddrtoname (struct sockaddr *sadr);
+void *ip_nametoaddr (char *name,size_t *len,int *family,char **canonical,
+ void **next);
+
+/* Return IP address string from socket address
+ * Accepts: socket address
+ * Returns: IP address as name string
+ */
+
+char *ip_sockaddrtostring (struct sockaddr *sadr)
+{
+ return (sadr->sa_family == PF_INET) ?
+ inet_ntoa (SADR4ADR (sadr)) : "NON-IPv4";
+}
+
+
+/* Return port from socket address
+ * Accepts: socket address
+ * Returns: port number or -1 if can't determine it
+ */
+
+long ip_sockaddrtoport (struct sockaddr *sadr)
+{
+ return (sadr->sa_family == PF_INET) ? ntohs (SADR4PORT (sadr)) : -1;
+}
+
+
+/* Return IP address from string
+ * Accepts: name string
+ * pointer to returned length
+ * pointer to returned address family
+ * Returns: address if valid, length and family updated, or NIL
+ */
+
+void *ip_stringtoaddr (char *text,size_t *len,int *family)
+{
+ unsigned long adr;
+ struct in_addr *ret;
+ /* get address */
+ if ((adr = inet_addr (text)) == -1) ret = NIL;
+ else { /* make in_addr */
+ ret = (struct in_addr *) fs_get (*len = ADR4LEN);
+ *family = AF_INET; /* IPv4 */
+ ret->s_addr = adr; /* set address */
+ }
+ return (void *) ret;
+}
+
+/* Create a maximum-size socket address
+ * Accepts: pointer to return maximum socket address length
+ * Returns: new, empty socket address of maximum size
+ */
+
+struct sockaddr *ip_newsockaddr (size_t *len)
+{
+ return (struct sockaddr *) memset (fs_get (SADRLEN),0,*len = SADRLEN);
+}
+
+
+/* Stuff a socket address
+ * Accepts: address family
+ * IPv4 address
+ * length of address (always 4 in IPv4)
+ * port number
+ * pointer to return socket address length
+ * Returns: socket address or NIL if error
+ */
+
+struct sockaddr *ip_sockaddr (int family,void *adr,size_t adrlen,
+ unsigned short port,size_t *len)
+{
+ struct sockaddr *sadr = ip_newsockaddr (len);
+ switch (family) { /* build socket address based upon family */
+ case AF_INET: /* IPv4 */
+ sadr->sa_family = PF_INET;
+ /* copy host address */
+ memcpy (&SADR4ADR (sadr),adr,adrlen);
+ /* copy port number in network format */
+ SADR4PORT (sadr) = htons (port);
+ *len = SADR4LEN;
+ break;
+ default: /* non-IP?? */
+ sadr->sa_family = PF_UNSPEC;
+ break;
+ }
+ return sadr;
+}
+
+/* Return name from socket address
+ * Accepts: socket address
+ * Returns: canonical name for that address or NIL if none
+ */
+
+char *ip_sockaddrtoname (struct sockaddr *sadr)
+{
+ struct hostent *he;
+ return ((sadr->sa_family == PF_INET) &&
+ (he = gethostbyaddr ((char *) &SADR4ADR (sadr),ADR4LEN,AF_INET))) ?
+ (char *) he->h_name : NIL;
+}
+
+
+/* Return address from name
+ * Accepts: name or NIL to return next address
+ * pointer to previous/returned length
+ * pointer to previous/returned address family
+ * pointer to previous/returned canonical name
+ * pointer to previous/return state for next-address calls
+ * Returns: address with length/family/canonical updated if needed, or NIL
+ */
+
+void *ip_nametoaddr (char *name,size_t *len,int *family,char **canonical,
+ void **next)
+{
+ char **adl,tmp[MAILTMPLEN];
+ struct hostent *he;
+ if (name) { /* first lookup? */
+ /* yes, do case-independent lookup */
+ if ((strlen (name) < MAILTMPLEN) &&
+ (he = gethostbyname (lcase (strcpy (tmp,name))))) {
+ adl = he->h_addr_list;
+ if (len) *len = he->h_length;
+ if (family) *family = he->h_addrtype;
+ if (canonical) *canonical = (char *) he->h_name;
+ if (next) *next = (void *) adl;
+ }
+ else { /* error */
+ adl = NIL;
+ if (len) *len = 0;
+ if (family) *family = 0;
+ if (canonical) *canonical = NIL;
+ if (next) *next = NIL;
+ }
+ }
+ /* return next in series */
+ else if (next && (adl = (char **) *next)) *next = ++adl;
+ else adl = NIL; /* failure */
+ return adl ? (void *) *adl : NIL;
+}
diff --git a/imap/src/osdep/unix/ip6_unix.c b/imap/src/osdep/unix/ip6_unix.c
new file mode 100644
index 00000000..c15dfbc0
--- /dev/null
+++ b/imap/src/osdep/unix/ip6_unix.c
@@ -0,0 +1,288 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX IPv6 routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 18 December 2003
+ * Last Edited: 30 August 2006
+ */
+
+
+/*
+ * There is some amazingly bad design in IPv6 sockets.
+ *
+ * Supposedly, the new getnameinfo() and getaddrinfo() functions create an
+ * abstraction that is not dependent upon IPv4 or IPv6. However, the
+ * definition of getnameinfo() requires that the caller pass the length of
+ * the sockaddr instead of deriving it from sa_family. The man page says
+ * that there's an sa_len member in the sockaddr, but actually there isn't.
+ * This means that any caller to getnameinfo() and getaddrinfo() has to know
+ * the size for the protocol family used by that sockaddr.
+ *
+ * The new sockaddr_in6 is bigger than the generic sockaddr (which is what
+ * connect(), accept(), bind(), getpeername(), getsockname(), etc. expect).
+ * Rather than increase the size of sockaddr, there's a new sockaddr_storage
+ * which is only usable for allocating space.
+ */
+
+#define SADRLEN sizeof (struct sockaddr_storage)
+
+#define SADR4(sadr) ((struct sockaddr_in *) sadr)
+#define SADR4LEN sizeof (struct sockaddr_in)
+#define SADR4ADR(sadr) SADR4 (sadr)->sin_addr
+#define ADR4LEN sizeof (struct in_addr)
+#define SADR4PORT(sadr) SADR4 (sadr)->sin_port
+
+#define SADR6(sadr) ((struct sockaddr_in6 *) sadr)
+#define SADR6LEN sizeof (struct sockaddr_in6)
+#define SADR6ADR(sadr) SADR6 (sadr)->sin6_addr
+#define ADR6LEN sizeof (struct in6_addr)
+#define SADR6PORT(sadr) SADR6 (sadr)->sin6_port
+
+
+/* IP abstraction layer */
+
+char *ip_sockaddrtostring (struct sockaddr *sadr);
+long ip_sockaddrtoport (struct sockaddr *sadr);
+void *ip_stringtoaddr (char *text,size_t *len,int *family);
+struct sockaddr *ip_newsockaddr (size_t *len);
+struct sockaddr *ip_sockaddr (int family,void *adr,size_t adrlen,
+ unsigned short port,size_t *len);
+char *ip_sockaddrtoname (struct sockaddr *sadr);
+void *ip_nametoaddr (char *name,size_t *len,int *family,char **canonical,
+ void **next);
+
+/* Return IP address string from socket address
+ * Accepts: socket address
+ * Returns: IP address as name string
+ */
+
+char *ip_sockaddrtostring (struct sockaddr *sadr)
+{
+ static char tmp[NI_MAXHOST];
+ switch (sadr->sa_family) {
+ case PF_INET: /* IPv4 */
+ if (!getnameinfo (sadr,SADR4LEN,tmp,NI_MAXHOST,NIL,NIL,NI_NUMERICHOST))
+ return tmp;
+ break;
+ case PF_INET6: /* IPv6 */
+ if (!getnameinfo (sadr,SADR6LEN,tmp,NI_MAXHOST,NIL,NIL,NI_NUMERICHOST))
+ return tmp;
+ break;
+ }
+ return "NON-IP";
+}
+
+
+/* Return port from socket address
+ * Accepts: socket address
+ * Returns: port number or -1 if can't determine it
+ */
+
+long ip_sockaddrtoport (struct sockaddr *sadr)
+{
+ switch (sadr->sa_family) {
+ case PF_INET:
+ return ntohs (SADR4PORT (sadr));
+ case PF_INET6:
+ return ntohs (SADR6PORT (sadr));
+ }
+ return -1;
+}
+
+/* Return IP address from string
+ * Accepts: name string
+ * pointer to returned length
+ * pointer to returned address family
+ * Returns: address if valid, length and family updated, or NIL
+ */
+
+void *ip_stringtoaddr (char *text,size_t *len,int *family)
+
+{
+ char tmp[MAILTMPLEN];
+ static struct addrinfo *hints;
+ struct addrinfo *ai;
+ void *adr = NIL;
+ if (!hints) { /* hints set up yet? */
+ hints = (struct addrinfo *) /* one-time setup */
+ memset (fs_get (sizeof (struct addrinfo)),0,sizeof (struct addrinfo));
+ hints->ai_family = AF_UNSPEC;/* allow any address family */
+ hints->ai_socktype = SOCK_STREAM;
+ /* numeric name only */
+ hints->ai_flags = AI_NUMERICHOST;
+ }
+ /* case-independent lookup */
+ if (text && (strlen (text) < MAILTMPLEN) &&
+ (!getaddrinfo (lcase (strcpy (tmp,text)),NIL,hints,&ai))) {
+ switch (*family = ai->ai_family) {
+ case AF_INET: /* IPv4 */
+ adr = fs_get (*len = ADR4LEN);
+ memcpy (adr,(void *) &SADR4ADR (ai->ai_addr),*len);
+ break;
+ case AF_INET6: /* IPv6 */
+ adr = fs_get (*len = ADR6LEN);
+ memcpy (adr,(void *) &SADR6ADR (ai->ai_addr),*len);
+ break;
+ }
+ freeaddrinfo (ai); /* free addrinfo */
+ }
+ return adr;
+}
+
+/* Create a maximum-size socket address
+ * Accepts: pointer to return maximum socket address length
+ * Returns: new, empty socket address of maximum size
+ */
+
+struct sockaddr *ip_newsockaddr (size_t *len)
+{
+ return (struct sockaddr *) memset (fs_get (SADRLEN),0,*len = SADRLEN);
+}
+
+
+/* Stuff a socket address
+ * Accepts: address family
+ * IPv4 address
+ * length of address
+ * port number
+ * pointer to return socket address length
+ * Returns: socket address
+ */
+
+struct sockaddr *ip_sockaddr (int family,void *adr,size_t adrlen,
+ unsigned short port,size_t *len)
+{
+ struct sockaddr *sadr = ip_newsockaddr (len);
+ switch (family) { /* build socket address based upon family */
+ case AF_INET: /* IPv4 */
+ sadr->sa_family = PF_INET;
+ /* copy host address */
+ memcpy (&SADR4ADR (sadr),adr,adrlen);
+ /* copy port number in network format */
+ SADR4PORT (sadr) = htons (port);
+ *len = SADR4LEN;
+ break;
+ case AF_INET6: /* IPv6 */
+ sadr->sa_family = PF_INET6;
+ /* copy host address */
+ memcpy (&SADR6ADR (sadr),adr,adrlen);
+ /* copy port number in network format */
+ SADR6PORT (sadr) = htons (port);
+ *len = SADR6LEN;
+ break;
+ default: /* non-IP?? */
+ sadr->sa_family = PF_UNSPEC;
+ break;
+ }
+ return sadr;
+}
+
+/* Return name from socket address
+ * Accepts: socket address
+ * Returns: canonical name for that address or NIL if none
+ */
+
+char *ip_sockaddrtoname (struct sockaddr *sadr)
+{
+ static char tmp[NI_MAXHOST];
+ switch (sadr->sa_family) {
+ case PF_INET: /* IPv4 */
+ if (!getnameinfo (sadr,SADR4LEN,tmp,NI_MAXHOST,NIL,NIL,NI_NAMEREQD))
+ return tmp;
+ break;
+ case PF_INET6: /* IPv6 */
+ if (!getnameinfo (sadr,SADR6LEN,tmp,NI_MAXHOST,NIL,NIL,NI_NAMEREQD))
+ return tmp;
+ break;
+ }
+ return NIL;
+}
+
+/* Return address from name
+ * Accepts: name or NIL to return next address
+ * pointer to previous/returned length
+ * pointer to previous/returned address family
+ * pointer to previous/returned canonical name
+ * pointer to previous/return state for next-address calls
+ * Returns: address with length/family/canonical updated if needed, or NIL
+ */
+
+void *ip_nametoaddr (char *name,size_t *len,int *family,char **canonical,
+ void **next)
+{
+ struct addrinfo *cur = NIL;
+ static struct addrinfo *hints;
+ static struct addrinfo *ai = NIL;
+ static char lcname[MAILTMPLEN];
+ if (!hints) { /* hints set up yet? */
+ hints = (struct addrinfo *) /* one-time setup */
+ memset (fs_get (sizeof (struct addrinfo)),0,sizeof (struct addrinfo));
+ /* allow any address family */
+ hints->ai_family = AF_UNSPEC;
+ hints->ai_socktype = SOCK_STREAM;
+ /* need canonical name */
+ hints->ai_flags = AI_CANONNAME;
+ }
+ if (name) { /* name supplied? */
+ if (ai) {
+ freeaddrinfo (ai); /* free old addrinfo */
+ ai = NIL;
+ }
+ /* case-independent lookup */
+ if ((strlen (name) < MAILTMPLEN) &&
+ (!getaddrinfo (lcase (strcpy (lcname,name)),NIL,hints,&ai))) {
+ cur = ai; /* current block */
+ if (canonical) /* set canonical name */
+ *canonical = cur->ai_canonname ? cur->ai_canonname : lcname;
+ /* remember as next block */
+ if (next) *next = (void *) ai;
+ }
+ else { /* error */
+ cur = NIL;
+ if (len) *len = 0;
+ if (family) *family = 0;
+ if (canonical) *canonical = NIL;
+ if (next) *next = NIL;
+ }
+ }
+ /* return next in series */
+ else if (next && (cur = ((struct addrinfo *) *next)->ai_next)) {
+ *next = cur; /* set as last address */
+ /* set canonical in case changed */
+ if (canonical && cur->ai_canonname) *canonical = cur->ai_canonname;
+ }
+
+ if (cur) { /* got data? */
+ if (family) *family = cur->ai_family;
+ switch (cur->ai_family) {
+ case AF_INET:
+ if (len) *len = ADR4LEN;
+ return (void *) &SADR4ADR (cur->ai_addr);
+ case AF_INET6:
+ if (len) *len = ADR6LEN;
+ return (void *) &SADR6ADR (cur->ai_addr);
+ }
+ }
+ if (len) *len = 0; /* error return */
+ return NIL;
+}
diff --git a/imap/src/osdep/unix/ipo_unix.c b/imap/src/osdep/unix/ipo_unix.c
new file mode 100644
index 00000000..665fb2e8
--- /dev/null
+++ b/imap/src/osdep/unix/ipo_unix.c
@@ -0,0 +1,181 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX IPv4 old routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 18 December 2003
+ * Last Edited: 30 August 2006
+ */
+
+#define SADRLEN sizeof (struct sockaddr)
+
+#define SADR4(sadr) ((struct sockaddr_in *) sadr)
+#define SADR4LEN sizeof (struct sockaddr_in)
+#define SADR4ADR(sadr) SADR4 (sadr)->sin_addr
+#define ADR4LEN sizeof (struct in_addr)
+#define SADR4PORT(sadr) SADR4 (sadr)->sin_port
+
+
+/* IP abstraction layer */
+
+char *ip_sockaddrtostring (struct sockaddr *sadr);
+long ip_sockaddrtoport (struct sockaddr *sadr);
+void *ip_stringtoaddr (char *text,size_t *len,int *family);
+struct sockaddr *ip_newsockaddr (size_t *len);
+struct sockaddr *ip_sockaddr (int family,void *adr,size_t adrlen,
+ unsigned short port,size_t *len);
+char *ip_sockaddrtoname (struct sockaddr *sadr);
+void *ip_nametoaddr (char *name,size_t *len,int *family,char **canonical,
+ void **next);
+
+/* Return IP address string from socket address
+ * Accepts: socket address
+ * Returns: IP address as name string
+ */
+
+char *ip_sockaddrtostring (struct sockaddr *sadr)
+{
+ return (sadr->sa_family == PF_INET) ?
+ inet_ntoa (SADR4ADR (sadr)) : "NON-IPv4";
+}
+
+
+/* Return port from socket address
+ * Accepts: socket address
+ * Returns: port number or -1 if can't determine it
+ */
+
+long ip_sockaddrtoport (struct sockaddr *sadr)
+{
+ return (sadr->sa_family == PF_INET) ? ntohs (SADR4PORT (sadr)) : -1;
+}
+
+
+/* Return IP address from string
+ * Accepts: name string
+ * pointer to returned length
+ * pointer to returned address family
+ * Returns: address if valid, length and family updated, or NIL
+ */
+
+void *ip_stringtoaddr (char *text,size_t *len,int *family)
+{
+ unsigned long adr;
+ struct in_addr *ret;
+ /* get address */
+ if ((adr = inet_addr (text)) == -1) ret = NIL;
+ else { /* make in_addr */
+ ret = (struct in_addr *) fs_get (*len = ADR4LEN);
+ *family = AF_INET; /* IPv4 */
+ ret->s_addr = adr; /* set address */
+ }
+ return (void *) ret;
+}
+
+/* Create a maximum-size socket address
+ * Accepts: pointer to return maximum socket address length
+ * Returns: new, empty socket address of maximum size
+ */
+
+struct sockaddr *ip_newsockaddr (size_t *len)
+{
+ return (struct sockaddr *) memset (fs_get (SADRLEN),0,*len = SADRLEN);
+}
+
+
+/* Stuff a socket address
+ * Accepts: address family
+ * IPv4 address
+ * length of address (always 4 in IPv4)
+ * port number
+ * pointer to return socket address length
+ * Returns: socket address or NIL if error
+ */
+
+struct sockaddr *ip_sockaddr (int family,void *adr,size_t adrlen,
+ unsigned short port,size_t *len)
+{
+ struct sockaddr *sadr = ip_newsockaddr (len);
+ switch (family) { /* build socket address based upon family */
+ case AF_INET: /* IPv4 */
+ sadr->sa_family = PF_INET;
+ /* copy host address */
+ memcpy (&SADR4ADR (sadr),adr,adrlen);
+ /* copy port number in network format */
+ SADR4PORT (sadr) = htons (port);
+ *len = SADR4LEN;
+ break;
+ default: /* non-IP?? */
+ sadr->sa_family = PF_UNSPEC;
+ break;
+ }
+ return sadr;
+}
+
+/* Return name from socket address
+ * Accepts: socket address
+ * Returns: canonical name for that address or NIL if none
+ */
+
+char *ip_sockaddrtoname (struct sockaddr *sadr)
+{
+ struct hostent *he;
+ return ((sadr->sa_family == PF_INET) &&
+ (he = gethostbyaddr ((char *) &SADR4ADR (sadr),ADR4LEN,AF_INET))) ?
+ (char *) he->h_name : NIL;
+}
+
+
+/* Return address from name
+ * Accepts: name or NIL to return next address
+ * pointer to previous/returned length
+ * pointer to previous/returned address family
+ * pointer to previous/returned canonical name
+ * pointer to previous/return state for next-address calls
+ * Returns: address with length/family/canonical updated if needed, or NIL
+ */
+
+void *ip_nametoaddr (char *name,size_t *len,int *family,char **canonical,
+ void **next)
+{
+ char tmp[MAILTMPLEN];
+ struct hostent *he;
+ void *ret = NIL;
+ if (name) { /* first lookup? */
+ /* yes, do case-independent lookup */
+ if ((strlen (name) < MAILTMPLEN) &&
+ (he = gethostbyname (lcase (strcpy (tmp,name))))) {
+ if (len) *len = he->h_length;
+ if (family) *family = he->h_addrtype;
+ if (canonical) *canonical = (char *) he->h_name;
+ if (next) *next = 0;
+ ret = he->h_addr; /* set result to this one and only block */
+ }
+ else { /* error */
+ if (len) *len = 0;
+ if (family) *family = 0;
+ if (canonical) *canonical = NIL;
+ if (next) *next = NIL;
+ }
+ }
+ return ret; /* return result */
+}
diff --git a/imap/src/osdep/unix/kerb_mit.c b/imap/src/osdep/unix/kerb_mit.c
new file mode 100644
index 00000000..82e6c936
--- /dev/null
+++ b/imap/src/osdep/unix/kerb_mit.c
@@ -0,0 +1,111 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MIT Kerberos routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 4 March 2003
+ * Last Edited: 30 August 2006
+ */
+
+#define PROTOTYPE(x) x
+#include <gssapi/gssapi_generic.h>
+#include <gssapi/gssapi_krb5.h>
+
+
+long kerberos_server_valid (void);
+long kerberos_try_kinit (OM_uint32 error);
+char *kerberos_login (char *user,char *authuser,int argc,char *argv[]);
+
+/* Kerberos server valid check
+ * Returns: T if have keytab, NIL otherwise
+ *
+ * Note that this routine will probably return T only if the process is root.
+ * This is alright since the server is probably still root at this point.
+ */
+
+long kerberos_server_valid ()
+{
+ krb5_context ctx;
+ krb5_keytab kt;
+ krb5_kt_cursor csr;
+ long ret = NIL;
+ /* make a context */
+ if (!krb5_init_context (&ctx)) {
+ /* get default keytab */
+ if (!krb5_kt_default (ctx,&kt)) {
+ /* can do server if have good keytab */
+ if (!krb5_kt_start_seq_get (ctx,kt,&csr) &&
+ !krb5_kt_end_seq_get (ctx,kt,&csr)) ret = LONGT;
+ krb5_kt_close (ctx,kt); /* finished with keytab */
+ }
+ krb5_free_context (ctx); /* finished with context */
+ }
+ return ret;
+}
+
+
+/* Kerberos check for missing or expired credentials
+ * Returns: T if should suggest running kinit, NIL otherwise
+ */
+
+long kerberos_try_kinit (OM_uint32 error)
+{
+ switch (error) {
+ case KRB5KRB_AP_ERR_TKT_EXPIRED:
+ case KRB5_FCC_NOFILE: /* MIT */
+ case KRB5_CC_NOTFOUND: /* Heimdal */
+ return LONGT;
+ }
+ return NIL;
+}
+
+/* Kerberos server log in
+ * Accepts: authorization ID as user name
+ * authentication ID as Kerberos principal
+ * argument count
+ * argument vector
+ * Returns: logged in user name if logged in, NIL otherwise
+ */
+
+char *kerberos_login (char *user,char *authuser,int argc,char *argv[])
+{
+ krb5_context ctx;
+ krb5_principal prnc;
+ char kuser[NETMAXUSER];
+ char *ret = NIL;
+ /* make a context */
+ if (!krb5_init_context (&ctx)) {
+ /* build principal */
+ if (!krb5_parse_name (ctx,authuser,&prnc)) {
+ /* can get local name for this principal? */
+ if (!krb5_aname_to_localname (ctx,prnc,NETMAXUSER-1,kuser)) {
+ /* yes, local name permitted login as user? */
+ if (authserver_login (user,kuser,argc,argv) ||
+ authserver_login (lcase (user),kuser,argc,argv))
+ ret = myusername (); /* yes, return user name */
+ }
+ krb5_free_principal (ctx,prnc);
+ }
+ krb5_free_context (ctx); /* finished with context */
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/unix/log_bsi.c b/imap/src/osdep/unix/log_bsi.c
new file mode 100644
index 00000000..5db7415d
--- /dev/null
+++ b/imap/src/osdep/unix/log_bsi.c
@@ -0,0 +1,55 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: BSI login
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Log in
+ * Accepts: login passwd struct
+ * argument count
+ * argument vector
+ * Returns: T if success, NIL otherwise
+ */
+
+long loginpw (struct passwd *pw,int argc,char *argv[])
+{
+ long ret = NIL;
+ uid_t euid = geteuid ();
+ login_cap_t *lc = login_getclass (pw->pw_class);
+ /* have class and can become user? */
+ if (lc && !seteuid (pw->pw_uid)) {
+ /* ask for approval */
+ if (auth_approve (lc,pw->pw_name,
+ (char *) mail_parameters (NIL,GET_SERVICENAME,NIL)) <= 0)
+ seteuid (euid); /* not approved, restore root euid */
+ else { /* approved */
+ seteuid (euid); /* restore former root euid first */
+ setsid (); /* ensure we are session leader */
+ /* log the guy in */
+ ret = !setusercontext (lc,pw,pw->pw_uid,LOGIN_SETALL&~LOGIN_SETPATH);
+ }
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/unix/log_cyg.c b/imap/src/osdep/unix/log_cyg.c
new file mode 100644
index 00000000..b60a4a7d
--- /dev/null
+++ b/imap/src/osdep/unix/log_cyg.c
@@ -0,0 +1,46 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Cygwin login
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Log in
+ * Accepts: login passwd struct
+ * argument count
+ * argument vector
+ * Returns: T if success, NIL otherwise
+ */
+
+long loginpw (struct passwd *pw,int argc,char *argv[])
+{
+ uid_t uid = pw->pw_uid;
+ /* must be same user name as last checkpw() */
+ if (!(cyg_user && !strcmp (pw->pw_name,cyg_user))) return NIL;
+ /* do the ImpersonateLoggedOnUser() */
+ cygwin_set_impersonation_token (cyg_hdl);
+
+ return !(setgid (pw->pw_gid) || initgroups (cyg_user,pw->pw_gid) ||
+ setuid (uid));
+}
diff --git a/imap/src/osdep/unix/log_old.c b/imap/src/osdep/unix/log_old.c
new file mode 100644
index 00000000..2cb10529
--- /dev/null
+++ b/imap/src/osdep/unix/log_old.c
@@ -0,0 +1,39 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Standard login for very old UNIX systems
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Log in
+ * Accepts: login passwd struct
+ * argument count
+ * argument vector
+ * Returns: T if success, NIL otherwise
+ */
+
+long loginpw (struct passwd *pw,int argc,char *argv[])
+{
+ return !(setgid (pw->pw_gid) || setuid (pw->pw_uid));
+}
diff --git a/imap/src/osdep/unix/log_os4.c b/imap/src/osdep/unix/log_os4.c
new file mode 100644
index 00000000..dec704e5
--- /dev/null
+++ b/imap/src/osdep/unix/log_os4.c
@@ -0,0 +1,58 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: OSF/1 (Digital UNIX) 4 login
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Log in
+ * Accepts: login passwd struct
+ * argument count
+ * argument vector
+ * Returns: T if success, NIL otherwise
+ */
+
+long loginpw (struct passwd *pw,int argc,char *argv[])
+{
+ int i;
+ char *s;
+ char *name = cpystr (pw->pw_name);
+ char *host = cpystr (tcp_clienthost ());
+ uid_t uid = pw->pw_uid;
+ long ret = NIL;
+ /* tie off address */
+ if (s = strchr (host,' ')) *s = '\0';
+ if (*host == '[') { /* convert [a.b.c.d] to a.b.c.d */
+ memmove (host,host+1,i = strlen (host + 2));
+ host[i] = '\0';
+ }
+ if (sia_become_user (checkpw_collect,argc,argv,host,name,NIL,NIL,NIL,NIL,
+ SIA_BEU_REALLOGIN|SIA_BEU_OKROOTDIR) != SIASUCCESS)
+ setreuid (0,0); /* make sure have root again */
+ /* probable success, complete login */
+ else ret = (!setreuid (0,0) && !setuid (uid));
+ fs_give ((void **) &name);
+ fs_give ((void **) &host);
+ return ret;
+}
diff --git a/imap/src/osdep/unix/log_sec.c b/imap/src/osdep/unix/log_sec.c
new file mode 100644
index 00000000..f2641896
--- /dev/null
+++ b/imap/src/osdep/unix/log_sec.c
@@ -0,0 +1,44 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: SecureWare login
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Log in
+ * Accepts: login passwd struct
+ * argument count
+ * argument vector
+ * Returns: T if success, NIL otherwise
+ */
+
+long loginpw (struct passwd *pw,int argc,char *argv[])
+{
+ uid_t uid = pw->pw_uid;
+ char *name = cpystr (pw->pw_name);
+ long ret = !(setluid (uid) || setgid (pw->pw_gid) ||
+ initgroups (name,pw->pw_gid) || setuid (uid));
+ fs_give ((void **) &name);
+ return ret;
+}
diff --git a/imap/src/osdep/unix/log_std.c b/imap/src/osdep/unix/log_std.c
new file mode 100644
index 00000000..a36fa88f
--- /dev/null
+++ b/imap/src/osdep/unix/log_std.c
@@ -0,0 +1,44 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Standard login
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Log in
+ * Accepts: login passwd struct
+ * argument count
+ * argument vector
+ * Returns: T if success, NIL otherwise
+ */
+
+long loginpw (struct passwd *pw,int argc,char *argv[])
+{
+ uid_t uid = pw->pw_uid;
+ char *name = cpystr (pw->pw_name);
+ long ret = !(setgid (pw->pw_gid) || initgroups (name,pw->pw_gid) ||
+ setuid (uid));
+ fs_give ((void **) &name);
+ return ret;
+}
diff --git a/imap/src/osdep/unix/log_sv4.c b/imap/src/osdep/unix/log_sv4.c
new file mode 100644
index 00000000..4738ce12
--- /dev/null
+++ b/imap/src/osdep/unix/log_sv4.c
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: SVR4 login
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Log in
+ * Accepts: login passwd struct
+ * argument count
+ * argument vector
+ * Returns: T if success, NIL otherwise
+ */
+
+long loginpw (struct passwd *pw,int argc,char *argv[])
+{
+ uid_t uid = pw->pw_uid;
+ char *name = cpystr (pw->pw_name);
+ long ret = !(setgid (pw->pw_gid) || initgroups (name) || setuid (uid));
+ fs_give ((void **) &name);
+ return ret;
+}
diff --git a/imap/src/osdep/unix/mbx.c b/imap/src/osdep/unix/mbx.c
new file mode 100644
index 00000000..1ece5d8d
--- /dev/null
+++ b/imap/src/osdep/unix/mbx.c
@@ -0,0 +1,1855 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MBX mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 3 October 1995
+ * Last Edited: 11 October 2007
+ */
+
+
+/* FILE TIME SEMANTICS
+ *
+ * The atime is the last read time of the file.
+ * The mtime is the last flags update time of the file.
+ * The ctime is the last write time of the file.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+
+/* Build parameters */
+
+#define HDRSIZE 2048
+
+
+/* Kludge to make Cygwin happy */
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+/* MBX I/O stream local data */
+
+typedef struct mbx_local {
+ unsigned int flagcheck: 1; /* if ping should sweep for flags */
+ unsigned int expok: 1; /* if expunging OK in ping */
+ unsigned int expunged : 1; /* if one or more expunged messages */
+ int fd; /* file descriptor for I/O */
+ int ld; /* lock file descriptor */
+ int ffuserflag; /* first free user flag */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* last snarf time */
+ unsigned long lastpid; /* PID of last writer */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ char lock[MAILTMPLEN]; /* buffer to write lock name */
+} MBXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MBXLOCAL *) stream->local)
+
+/* Function prototypes */
+
+DRIVER *mbx_valid (char *name);
+int mbx_isvalid (MAILSTREAM **stream,char *name,char *tmp,int *ld,char *lock,
+ long flags);
+void *mbx_parameters (long function,void *value);
+void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void mbx_list (MAILSTREAM *stream,char *ref,char *pat);
+void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mbx_create (MAILSTREAM *stream,char *mailbox);
+long mbx_delete (MAILSTREAM *stream,char *mailbox);
+long mbx_rename (MAILSTREAM *stream,char *old,char *newname);
+long mbx_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *mbx_open (MAILSTREAM *stream);
+void mbx_close (MAILSTREAM *stream,long options);
+void mbx_abort (MAILSTREAM *stream);
+void mbx_flags (MAILSTREAM *stream,char *sequence,long flags);
+char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags);
+long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long mbx_ping (MAILSTREAM *stream);
+void mbx_check (MAILSTREAM *stream);
+long mbx_expunge (MAILSTREAM *stream,char *sequence,long options);
+void mbx_snarf (MAILSTREAM *stream);
+long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+char *mbx_file (char *dst,char *name);
+long mbx_parse (MAILSTREAM *stream);
+MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok);
+unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
+void mbx_update_header (MAILSTREAM *stream);
+void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags);
+unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size,char **hdr);
+unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed,
+ long flags);
+long mbx_flaglock (MAILSTREAM *stream);
+
+/* MBX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mbxdriver = {
+ "mbx", /* driver name */
+ DR_LOCAL|DR_MAIL|DR_CRLF|DR_LOCKING,
+ /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ mbx_valid, /* mailbox is valid for us */
+ mbx_parameters, /* manipulate parameters */
+ mbx_scan, /* scan mailboxes */
+ mbx_list, /* list mailboxes */
+ mbx_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ mbx_create, /* create mailbox */
+ mbx_delete, /* delete mailbox */
+ mbx_rename, /* rename mailbox */
+ mbx_status, /* status of mailbox */
+ mbx_open, /* open mailbox */
+ mbx_close, /* close mailbox */
+ mbx_flags, /* fetch message "fast" attributes */
+ mbx_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mbx_header, /* fetch message header */
+ mbx_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ mbx_flag, /* modify flags */
+ mbx_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mbx_ping, /* ping mailbox to see if still alive */
+ mbx_check, /* check for new messages */
+ mbx_expunge, /* expunge deleted messages */
+ mbx_copy, /* copy messages to another mailbox */
+ mbx_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mbxproto = {&mbxdriver};
+
+/* MBX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mbx_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ int fd = mbx_isvalid (NIL,name,tmp,NIL,NIL,NIL);
+ if (fd < 0) return NIL;
+ close (fd); /* don't need the fd now */
+ return &mbxdriver;
+}
+
+
+/* MBX mail test for valid mailbox
+ * Accepts: returned stream with valid mailbox keywords
+ * mailbox name
+ * scratch buffer
+ * returned lock fd
+ * returned lock name
+ * RW flags or NIL for readonly
+ * Returns: file descriptor if valid, NIL otherwise
+ */
+
+#define MBXISVALIDNOUID 0x1 /* RW, don't do UID action */
+#define MBXISVALIDUID 0x2 /* RW, do UID action */
+
+int mbx_isvalid (MAILSTREAM **stream,char *name,char *tmp,int *ld,char *lock,
+ long flags)
+{
+ int fd,upd;
+ int ret = -1;
+ unsigned long i;
+ long j,k;
+ off_t pos;
+ char c,*s,*t,hdr[HDRSIZE];
+ struct stat sbuf;
+ time_t tp[2];
+ int error = EINVAL; /* assume invalid argument */
+ if (ld) *ld = -1; /* initially no lock */
+ if ((s = mbx_file (tmp,name)) && !stat (s,&sbuf) &&
+ ((fd = open (tmp,(flags ? O_RDWR : O_RDONLY)|O_BINARY,NIL)) >= 0)) {
+ error = -1; /* bogus format */
+ /* I love cretinous C compilers -- don't you? */
+ if (read (fd,hdr,HDRSIZE) == HDRSIZE)
+ if ((hdr[0] == '*') && (hdr[1] == 'm') && (hdr[2] == 'b') &&
+ (hdr[3] == 'x') && (hdr[4] == '*') && (hdr[5] == '\015') &&
+ (hdr[6] == '\012') && isxdigit (hdr[7]) && isxdigit (hdr[8]))
+ if (isxdigit (hdr[9]) && isxdigit (hdr[10]) && isxdigit (hdr[11]) &&
+ isxdigit (hdr[12]) && isxdigit (hdr[13]) && isxdigit (hdr[14]) &&
+ isxdigit (c = hdr[15]) && isxdigit (hdr[16]))
+ if (isxdigit (hdr[17]) && isxdigit (hdr[18]) &&
+ isxdigit (hdr[19]) && isxdigit (hdr[20]) &&
+ isxdigit (hdr[21]) && isxdigit (hdr[22]) &&
+ (hdr[23] == '\015') && (hdr[24] == '\012')) {
+ ret = fd; /* mbx format */
+
+ if (stream) { /* lock if making mini-stream */
+ if (flock (fd,LOCK_SH) ||
+ (flags && ((*ld = lockfd (fd,lock,LOCK_EX)) < 0))) ret = -1;
+ /* reread data now that locked */
+ else if (lseek (fd,0,L_SET) ||
+ (read (fd,hdr,HDRSIZE) != HDRSIZE)) ret = -1;
+ else {
+ *stream = (MAILSTREAM *) memset (fs_get (sizeof (MAILSTREAM)),
+ 0,sizeof (MAILSTREAM));
+ hdr[15] = '\0'; /* tie off UIDVALIDITY */
+ (*stream)->uid_validity = strtoul (hdr+7,NIL,16);
+ hdr[15] = c; /* now get UIDLAST */
+ (*stream)->uid_last = strtoul (hdr+15,NIL,16);
+ /* parse user flags */
+ for (i = 0, s = hdr + 25;
+ (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s);
+ i++, s = t + 2) {
+ *t = '\0'; /* tie off flag */
+ if (strlen (s) <= MAXUSERFLAG)
+ (*stream)->user_flags[i] = cpystr (s);
+ }
+ /* make sure have true UIDLAST */
+ if (flags & MBXISVALIDUID) {
+ for (upd = NIL,pos = 2048, k = 0; pos < sbuf.st_size;
+ pos += (j + k)) {
+ /* read header for this message */
+ lseek (fd,pos,L_SET);
+ if ((j = read (fd,hdr,64)) >= 0) {
+ hdr[j] = '\0';
+ if ((s = strchr (hdr,'\015')) && (s[1] == '\012')) {
+ *s = '\0';
+ k = s + 2 - hdr;
+ if ((s = strchr (hdr,',')) &&
+ (j = strtol (s+1,&s,10)) && (*s == ';') &&
+ (s = strchr (s+1,'-'))) {
+ /* get UID if there is any */
+ i = strtoul (++s,&t,16);
+ if (!*t && (t == (s + 8)) &&
+ (i <= (*stream)->uid_last)) {
+ if (!i) {
+ lseek (fd,pos + s - hdr,L_SET);
+ sprintf (hdr,"%08lx",++(*stream)->uid_last);
+ write (fd,hdr,8);
+ upd = T;
+ }
+ continue;
+ }
+ }
+ }
+ ret = -1; /* error, give up */
+ *stream = mail_close (*stream);
+ pos = sbuf.st_size + 1;
+ j = k = 0;
+ }
+ }
+
+ if (upd) { /* need to update hdr with new UIDLAST? */
+ lseek (fd,15,L_SET);
+ sprintf (hdr,"%08lx",(*stream)->uid_last);
+ write (fd,hdr,8);
+ }
+ }
+ }
+ }
+ }
+ if (ret != fd) close (fd); /* close the file */
+ else lseek (fd,0,L_SET); /* else rewind to start */
+ /* \Marked status? */
+ if (sbuf.st_ctime > sbuf.st_atime) {
+ tp[0] = sbuf.st_atime; /* preserve atime and mtime */
+ tp[1] = sbuf.st_mtime;
+ utime (tmp,tp); /* set the times */
+ }
+ }
+ /* in case INBOX but not mbx format */
+ else if (((error = errno) == ENOENT) && !compare_cstring (name,"INBOX"))
+ error = -1;
+ if ((ret < 0) && ld && (*ld >= 0)) {
+ unlockfd (*ld,lock);
+ *ld = -1;
+ }
+ errno = error; /* return as last error */
+ return ret; /* return what we should */
+}
+
+/* MBX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mbx_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = mbx_file ((char *) value,"INBOX");
+ break;
+ case SET_ONETIMEEXPUNGEATPING:
+ if (value) ((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T;
+ case GET_ONETIMEEXPUNGEATPING:
+ if (value) ret = (void *)
+ (((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL);
+ break;
+ }
+ return ret;
+}
+
+
+/* MBX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MBX mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mbx_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MBX mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MBX mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,*t,mbx[MAILTMPLEN],tmp[HDRSIZE];
+ long ret = NIL;
+ int i,fd;
+ if (!(s = mbx_file (mbx,mailbox))) {
+ sprintf (mbx,"Can't create %.80s: invalid name",mailbox);
+ MM_LOG (mbx,ERROR);
+ }
+ /* create underlying file */
+ else if (dummy_create_path (stream,s,get_dir_protection (mailbox))) {
+ /* done if made directory */
+ if ((s = strrchr (s,'/')) && !s[1]) return T;
+ if ((fd = open (mbx,O_WRONLY|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else {
+ memset (tmp,'\0',HDRSIZE);/* initialize header */
+ sprintf (s = tmp,"*mbx*\015\012%08lx00000000\015\012",
+ (unsigned long) time (0));
+ for (i = 0; i < NUSERFLAGS; ++i) {
+ t = (stream && stream->user_flags[i]) ? stream->user_flags[i] :
+ ((t = default_user_flag (i)) ? t : "");
+ sprintf (s += strlen (s),"%s\015\012",t);
+ }
+ if (write (fd,tmp,HDRSIZE) != HDRSIZE) {
+ sprintf (tmp,"Can't initialize mailbox node %.80s: %s",
+ mbx,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else ret = T; /* success */
+ close (fd); /* close file */
+ }
+ }
+ /* set proper protections */
+ return ret ? set_mbx_protections (mailbox,mbx) : NIL;
+}
+
+
+/* MBX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return mbx_rename (stream,mailbox,NIL);
+}
+
+/* MBX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = LONGT;
+ char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ if (!mbx_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'/')) && !s[1])))) {
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else if ((fd = open (file,O_RDWR|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get parse/append permission */
+ if ((ld = lockfd (fd,lock,LOCK_EX)) < 0) {
+ MM_LOG ("Unable to lock rename mailbox",ERROR);
+ return NIL;
+ }
+ /* lock out other users */
+ if (flock (fd,LOCK_EX|LOCK_NB)) {
+ close (fd); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ MM_LOG (tmp,ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return NIL;
+ }
+
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if (s = strrchr (tmp,'/')) {
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* superior name doesn't exist, create it */
+ if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp,get_dir_protection (newname)))
+ ret = NIL;
+ else *s = c; /* restore full name */
+ }
+ /* rename the file */
+ if (ret && rename (file,tmp)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ else if (unlink (file)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ flock (fd,LOCK_UN); /* release lock on the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ close (fd); /* close the file */
+ /* recreate file if renamed INBOX */
+ if (ret && !compare_cstring (old,"INBOX")) mbx_create (NIL,"INBOX");
+ return ret; /* return success */
+}
+
+/* MBX Mail status
+ * Accepts: mail stream
+ * mailbox name
+ * status flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_status (MAILSTREAM *stream,char *mbx,long flags)
+{
+ MAILSTATUS status;
+ unsigned long i;
+ MAILSTREAM *tstream = NIL;
+ MAILSTREAM *systream = NIL;
+ /* make temporary stream (unless this mbx) */
+ if (!stream && !(stream = tstream =
+ mail_open (NIL,mbx,OP_READONLY|OP_SILENT)))
+ return NIL;
+ status.flags = flags; /* return status values */
+ status.messages = stream->nmsgs;
+ status.recent = stream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
+ if (!mail_elt (stream,i)->seen) status.unseen++;
+ status.uidnext = stream->uid_last + 1;
+ status.uidvalidity = stream->uid_validity;
+ /* calculate post-snarf results */
+ if (!status.recent && stream->inbox &&
+ (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) {
+ status.messages += systream->nmsgs;
+ status.recent += systream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1; i <= systream->nmsgs; i++)
+ if (!mail_elt (systream,i)->seen) status.unseen++;
+ /* kludge but probably good enough */
+ status.uidnext += systream->nmsgs;
+ }
+ MM_STATUS(stream,mbx,&status);/* pass status to main program */
+ if (tstream) mail_close (tstream);
+ if (systream) mail_close (systream);
+ return T; /* success */
+}
+
+/* MBX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mbx_open (MAILSTREAM *stream)
+{
+ int fd,ld;
+ short silent;
+ char tmp[MAILTMPLEN];
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&mbxproto);
+ if (stream->local) fatal ("mbx recycle stream");
+ /* canonicalize the mailbox name */
+ if (!mbx_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ MM_LOG (tmp,ERROR);
+ }
+ if (stream->rdonly ||
+ (fd = open (tmp,O_RDWR|O_BINARY,NIL)) < 0) {
+ if ((fd = open (tmp,O_RDONLY|O_BINARY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else if (!stream->rdonly) { /* got it, but readonly */
+ MM_LOG ("Can't get write access to mailbox, access is readonly",WARN);
+ stream->rdonly = T;
+ }
+ }
+
+ stream->local = memset (fs_get (sizeof (MBXLOCAL)),NIL,sizeof (MBXLOCAL));
+ LOCAL->fd = fd; /* bind the file */
+ LOCAL->ld = -1; /* no flaglock */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ /* get parse/append permission */
+ if ((ld = lockfd (LOCAL->fd,tmp,LOCK_EX)) < 0) {
+ MM_LOG ("Unable to lock open mailbox",ERROR);
+ return NIL;
+ }
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH); /* lock the file */
+ (*bn) (BLOCK_NONE,NIL);
+ unlockfd (ld,tmp); /* release shared parse permission */
+ LOCAL->filesize = HDRSIZE; /* initialize parsed file size */
+ /* time not set up yet */
+ LOCAL->lastsnarf = LOCAL->filetime = 0;
+ LOCAL->expok = LOCAL->flagcheck = NIL;
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ silent = stream->silent; /* defer events */
+ stream->silent = T;
+ if (mbx_ping (stream) && !stream->nmsgs)
+ MM_LOG ("Mailbox is empty",(long) NIL);
+ stream->silent = silent; /* now notify upper level */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,stream->recent);
+ if (!LOCAL) return NIL; /* failure if stream died */
+ stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
+ stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ stream->kwd_create = (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ?
+ NIL : T; /* can we create new user flags? */
+ return stream; /* return stream to caller */
+}
+
+/* MBX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mbx_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ /* do an expunge if requested */
+ if (options & CL_EXPUNGE) mbx_expunge (stream,NIL,NIL);
+ else { /* otherwise do a checkpoint to purge */
+ LOCAL->expok = T; /* possible expunged messages */
+ mbx_ping (stream);
+ }
+ stream->silent = silent; /* restore previous status */
+ mbx_abort (stream);
+ }
+}
+
+
+/* MBX mail abort stream
+ * Accepts: MAIL stream
+ */
+
+void mbx_abort (MAILSTREAM *stream)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ flock (LOCAL->fd,LOCK_UN); /* unlock local file */
+ close (LOCAL->fd); /* close the local file */
+ /* free local text buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+
+/* MBX mail fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ * Sniffs at file to see if some other process changed the flags
+ */
+
+void mbx_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned long i;
+ if (mbx_ping (stream) && /* ping mailbox, get new status for messages */
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence && !elt->valid)
+ mbx_elt (stream,i,NIL);
+}
+
+/* MBX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ unsigned long i;
+ char *s;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get header position, possibly header */
+ i = mbx_hdrpos (stream,msgno,length,&s);
+ if (!s) { /* mbx_hdrpos() returned header? */
+ lseek (LOCAL->fd,i,L_SET); /* no, get to header position */
+ /* is buffer big enough? */
+ if (*length > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = *length) + 1);
+ }
+ /* slurp the data */
+ read (LOCAL->fd,s = LOCAL->buf,*length);
+ }
+ s[*length] = '\0'; /* tie off string */
+ return s;
+}
+
+/* MBX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ FDDATA d;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ /* get message status */
+ elt = mbx_elt (stream,msgno,NIL);
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen && mbx_flaglock (stream)) {
+ elt->seen = T; /* mark message as seen */
+ /* recalculate status */
+ mbx_update_status (stream,msgno,NIL);
+ MM_FLAGS (stream,msgno);
+ /* update flags */
+ mbx_flag (stream,NIL,NIL,NIL);
+ }
+ if (!LOCAL) return NIL; /* mbx_flaglock() could have aborted */
+ /* find header position */
+ i = mbx_hdrpos (stream,msgno,&j,NIL);
+ d.fd = LOCAL->fd; /* set up file descriptor */
+ d.pos = i + j;
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE;
+ INIT (bs,fd_string,&d,elt->rfc822_size - j);
+ return LONGT; /* success */
+}
+
+/* MBX mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ * Unlocks flag lock
+ */
+
+void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ time_t tp[2];
+ struct stat sbuf;
+ unsigned long oldpid = LOCAL->lastpid;
+ /* make sure the update takes */
+ if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld >= 0)) {
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ /* we are the last flag updater */
+ LOCAL->lastpid = (unsigned long) getpid ();
+ /* update header if needed */
+ if (((LOCAL->ffuserflag < NUSERFLAGS) &&
+ stream->user_flags[LOCAL->ffuserflag]) || (oldpid != LOCAL->lastpid))
+ mbx_update_header (stream);
+ tp[0] = time (0); /* make sure read comes after all that */
+ utime (stream->mailbox,tp);
+ }
+ if (LOCAL->ld >= 0) { /* unlock now */
+ unlockfd (LOCAL->ld,LOCAL->lock);
+ LOCAL->ld = -1;
+ }
+}
+
+
+/* MBX mail per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ if (mbx_flaglock (stream)) mbx_update_status (stream,elt->msgno,NIL);
+}
+
+/* MBX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long mbx_ping (MAILSTREAM *stream)
+{
+ unsigned long i,pos;
+ long ret = NIL;
+ int ld;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ struct stat sbuf;
+ if (stream && LOCAL) { /* only if stream already open */
+ int snarf = stream->inbox && !stream->rdonly;
+ ret = LONGT; /* assume OK */
+ fstat (LOCAL->fd,&sbuf); /* get current file poop */
+ /* allow expunge if permitted at ping */
+ if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T;
+ /* if external modification */
+ if (LOCAL->filetime && (LOCAL->filetime < sbuf.st_mtime))
+ LOCAL->flagcheck = T; /* upgrade to flag checking */
+ /* new mail or flagcheck handling needed? */
+ if (((sbuf.st_size - LOCAL->filesize) || LOCAL->flagcheck ||
+ !stream->nmsgs || snarf) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) >= 0)) {
+ /* reparse header if not flagchecking */
+ if (!LOCAL->flagcheck) ret = mbx_parse (stream);
+ /* sweep mailbox for changed message status */
+ else if (ret = mbx_parse (stream)) {
+ unsigned long recent = 0;
+ LOCAL->filetime = sbuf.st_mtime;
+ for (i = 1; i <= stream->nmsgs; )
+ if (elt = mbx_elt (stream,i,LOCAL->expok)) {
+ if (elt->recent) ++recent;
+ ++i;
+ }
+ mail_recent (stream,recent);
+ LOCAL->flagcheck = NIL; /* got all the updates */
+ }
+ /* always reparse header at least */
+ if (ret && snarf) { /* snarf new messages if still OK */
+ mbx_snarf (stream);
+ /* parse snarfed messages */
+ ret = mbx_parse (stream);
+ }
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ if (ret) { /* must still be alive */
+ if (!LOCAL->expunged) /* look for holes if none known yet */
+ for (i = 1, pos = HDRSIZE;
+ !LOCAL->expunged && (i <= stream->nmsgs);
+ i++, pos += elt->private.special.text.size + elt->rfc822_size)
+ if ((elt = mail_elt (stream,i))->private.special.offset != pos)
+ LOCAL->expunged = T;/* found a hole */
+ /* burp any holes */
+ if (LOCAL->expunged && !stream->rdonly) {
+ if (mbx_rewrite (stream,&i,NIL)) fatal ("expunge on check");
+ if (i) { /* any space reclaimed? */
+ LOCAL->expunged = NIL;/* no more pending expunge */
+ sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",i);
+ MM_LOG (LOCAL->buf,(long) NIL);
+ }
+ }
+ LOCAL->expok = NIL; /* no more expok */
+ }
+ }
+ return ret; /* return result of the parse */
+}
+
+/* MBX mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void mbx_check (MAILSTREAM *stream)
+{
+ if (LOCAL) LOCAL->expok = T; /* mark that a check is desired */
+ if (mbx_ping (stream)) MM_LOG ("Check completed",(long) NIL);
+}
+
+
+/* MBX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T if success, NIL if failure
+ */
+
+long mbx_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ unsigned long nexp,reclaimed;
+ if (ret = sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) {
+ if (!mbx_ping (stream)); /* do nothing if stream dead */
+ else if (stream->rdonly) /* won't do on readonly files! */
+ MM_LOG ("Expunge ignored on readonly mailbox",WARN);
+ /* if expunged any messages */
+ else if (nexp = mbx_rewrite (stream,&reclaimed,sequence ? -1 : 1)) {
+ sprintf (LOCAL->buf,"Expunged %lu messages",nexp);
+ MM_LOG (LOCAL->buf,(long) NIL);
+ }
+ else if (reclaimed) { /* or if any prior expunged space reclaimed */
+ sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed);
+ MM_LOG (LOCAL->buf,(long) NIL);
+ }
+ else MM_LOG ("No messages deleted, so no update needed",(long) NIL);
+ }
+ return ret;
+}
+
+/* MBX mail snarf messages from system inbox
+ * Accepts: MAIL stream, already locked
+ */
+
+void mbx_snarf (MAILSTREAM *stream)
+{
+ unsigned long i = 0;
+ unsigned long j,r,hdrlen,txtlen;
+ struct stat sbuf;
+ char *hdr,*txt,tmp[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ MAILSTREAM *sysibx = NIL;
+ /* give up if can't get exclusive permission */
+ if ((time (0) >= (LOCAL->lastsnarf +
+ (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) &&
+ strcmp (sysinbox (),stream->mailbox)) {
+ MM_CRITICAL (stream); /* go critical */
+ /* sizes match and anything in sysinbox? */
+ if (!stat (sysinbox (),&sbuf) && sbuf.st_size &&
+ !fstat (LOCAL->fd,&sbuf) && (sbuf.st_size == LOCAL->filesize) &&
+ (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
+ (!sysibx->rdonly) && (r = sysibx->nmsgs)) {
+ /* yes, go to end of file in our mailbox */
+ lseek (LOCAL->fd,sbuf.st_size,L_SET);
+ /* for each message in sysibx mailbox */
+ while (r && (++i <= sysibx->nmsgs)) {
+ /* snarf message from system INBOX */
+ hdr = cpystr (mail_fetchheader_full (sysibx,i,NIL,&hdrlen,NIL));
+ txt = mail_fetchtext_full (sysibx,i,&txtlen,FT_PEEK);
+ /* if have a message */
+ if (j = hdrlen + txtlen) {
+ /* build header line */
+ mail_date (LOCAL->buf,elt = mail_elt (sysibx,i));
+ sprintf (LOCAL->buf + strlen (LOCAL->buf),
+ ",%lu;00000000%04x-00000000\015\012",j,(unsigned)
+ ((fSEEN * elt->seen) +
+ (fDELETED * elt->deleted) + (fFLAGGED * elt->flagged) +
+ (fANSWERED * elt->answered) + (fDRAFT * elt->draft)));
+ /* copy message */
+ if ((write (LOCAL->fd,LOCAL->buf,strlen (LOCAL->buf)) < 0) ||
+ (write (LOCAL->fd,hdr,hdrlen) < 0) ||
+ (write (LOCAL->fd,txt,txtlen) < 0)) r = 0;
+ }
+ fs_give ((void **) &hdr);
+ }
+
+ /* make sure all the updates take */
+ if (fsync (LOCAL->fd)) r = 0;
+ if (r) { /* delete all the messages we copied */
+ if (r == 1) strcpy (tmp,"1");
+ else sprintf (tmp,"1:%lu",r);
+ mail_setflag (sysibx,tmp,"\\Deleted");
+ mail_expunge (sysibx); /* now expunge all those messages */
+ }
+ else {
+ sprintf (LOCAL->buf,"Can't copy new mail: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,WARN);
+ ftruncate (LOCAL->fd,sbuf.st_size);
+ }
+ fstat (LOCAL->fd,&sbuf); /* yes, get current file size */
+ LOCAL->filetime = sbuf.st_mtime;
+ }
+ if (sysibx) mail_close (sysibx);
+ MM_NOCRITICAL (stream); /* release critical */
+ LOCAL->lastsnarf = time (0);/* note time of last snarf */
+ }
+}
+
+/* MBX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ time_t tp[2];
+ MESSAGECACHE *elt;
+ unsigned long i,j,k,m;
+ long ret = LONGT;
+ int fd,ld;
+ char *s,*t,file[MAILTMPLEN],lock[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ MAILSTREAM *dstream = NIL;
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* make sure valid mailbox */
+ if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
+ cu ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid MBX-format mailbox name: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a MBX-format mailbox: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ MM_CRITICAL (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
+
+ /* for each requested message */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.special.text.size,L_SET);
+ mail_date(LOCAL->buf,elt);/* build target header */
+ /* get target keyword mask */
+ for (j = elt->user_flags, k = 0; j; )
+ if (s = stream->user_flags[find_rightmost_bit (&j)])
+ for (m = 0; (m < NUSERFLAGS) && (t = dstream->user_flags[m]); m++)
+ if (!compare_cstring (s,t) && (k |= 1 << m)) break;
+ sprintf (LOCAL->buf+strlen(LOCAL->buf),",%lu;%08lx%04x-%08lx\015\012",
+ elt->rfc822_size,k,(unsigned)
+ ((fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)),cu ? ++dstream->uid_last : 0);
+ /* write target header */
+ if (ret = (write (fd,LOCAL->buf,strlen (LOCAL->buf)) > 0)) {
+ for (k = elt->rfc822_size; ret && (j = min (k,LOCAL->buflen)); k -= j){
+ read (LOCAL->fd,LOCAL->buf,j);
+ ret = write (fd,LOCAL->buf,j) >= 0;
+ }
+ if (cu) { /* need to pass back new UID? */
+ mail_append_set (source,mail_uid (stream,i));
+ mail_append_set (dest,dstream->uid_last);
+ }
+ }
+ }
+
+ /* make sure all the updates take */
+ if (!(ret && (ret = !fsync (fd)))) {
+ sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ }
+ if (cu && ret) { /* return sets if doing COPYUID */
+ (*cu) (stream,mailbox,dstream->uid_validity,source,dest);
+ lseek (fd,15,L_SET); /* update UIDLAST */
+ sprintf (LOCAL->buf,"%08lx",dstream->uid_last);
+ write (fd,LOCAL->buf,8);
+ }
+ else { /* flush any sets we may have built */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */
+ /* else preserve \Marked status */
+ else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
+ tp[1] = sbuf.st_mtime; /* preserve mtime */
+ utime (file,tp); /* set the times */
+ close (fd); /* close the file */
+ MM_NOCRITICAL (stream); /* release critical */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE) && mbx_flaglock (stream)) {
+ for (i = 1; i <= stream->nmsgs; i++) if (mail_elt (stream,i)->sequence) {
+ /* mark message deleted */
+ mbx_elt (stream,i,NIL)->deleted = T;
+ /* recalculate status */
+ mbx_update_status (stream,i,NIL);
+ }
+ /* update flags */
+ mbx_flag (stream,NIL,NIL,NIL);
+ }
+ if (dstream != stream) mail_close (dstream);
+ return ret;
+}
+
+/* MBX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd,ld;
+ char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ time_t tp[2];
+ FILE *df;
+ MESSAGECACHE elt;
+ long f;
+ unsigned long i,uf;
+ STRING *message;
+ long ret = NIL;
+ MAILSTREAM *dstream = NIL;
+ appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ /* make sure valid mailbox */
+ if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
+ au ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* can create INBOX here */
+ mbx_create (dstream = stream ? stream : user_flags (&mbxproto),"INBOX");
+ if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
+ au ? MBXISVALIDUID : MBXISVALIDNOUID)) >= 0)
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid MBX-format mailbox name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MBX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+
+ /* get first message */
+ if (!MM_APPEND (af) (dstream,data,&flags,&date,&message)) close (fd);
+ else if (!(df = fdopen (fd,"r+b"))) {
+ MM_LOG ("Unable to reopen append mailbox",ERROR);
+ close (fd);
+ }
+ else {
+ MM_CRITICAL (dstream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ fseek (df,sbuf.st_size,SEEK_SET);
+ errno = 0;
+ for (ret = LONGT; ret && message; ) {
+ if (!SIZE (message)) { /* guard against zero-length */
+ MM_LOG ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ f = mail_parse_flags (dstream,flags,&uf);
+ if (date) { /* parse date if given */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* mark failure */
+ break;
+ }
+ mail_date (tmp,&elt); /* write preseved date */
+ }
+ else internal_date (tmp); /* get current date in IMAP format */
+ /* write header */
+ if (fprintf (df,"%s,%lu;%08lx%04lx-%08lx\015\012",tmp,i = SIZE (message),
+ uf,(unsigned long) f,au ? ++dstream->uid_last : 0) < 0)
+ ret = NIL;
+ else { /* write message */
+ size_t j;
+ if (!message->cursize) SETPOS (message,GETPOS (message));
+ while (i && (j = fwrite (message->curpos,1,message->cursize,df))) {
+ i -= j;
+ SETPOS (message,GETPOS (message) + j);
+ }
+ /* get next message */
+ if (i || !MM_APPEND (af) (dstream,data,&flags,&date,&message))
+ ret = NIL;
+ else if (au) mail_append_set (dst,dstream->uid_last);
+ }
+ }
+
+ /* if error... */
+ if (!ret || (fflush (df) == EOF)) {
+ /* revert file */
+ ftruncate (fd,sbuf.st_size);
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ if (errno) {
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ ret = NIL;
+ }
+ if (au && ret) { /* return sets if doing APPENDUID */
+ (*au) (mailbox,dstream->uid_validity,dst);
+ fseek (df,15,SEEK_SET); /* update UIDLAST */
+ fprintf (df,"%08lx",dstream->uid_last);
+ }
+ else mail_free_searchset (&dst);
+ /* set atime to now-1 if successful copy */
+ if (ret) tp[0] = time (0) - 1;
+ /* else preserve \Marked status */
+ else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
+ tp[1] = sbuf.st_mtime; /* preserve mtime */
+ utime (file,tp); /* set the times */
+ fclose (df); /* close the file */
+ MM_NOCRITICAL (dstream); /* release critical */
+ }
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ if (dstream != stream) mail_close (dstream);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* MBX mail generate file string
+ * Accepts: temporary buffer to write into
+ * mailbox name string
+ * Returns: local file string or NIL if failure
+ */
+
+char *mbx_file (char *dst,char *name)
+{
+ char *s = mailboxfile (dst,name);
+ return (s && !*s) ? mailboxfile (dst,"~/INBOX") : s;
+}
+
+/* MBX mail parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long mbx_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = NIL;
+ unsigned char c,*s,*t,*x;
+ char tmp[MAILTMPLEN];
+ unsigned long i,j,k,m;
+ off_t curpos = LOCAL->filesize;
+ unsigned long nmsgs = stream->nmsgs;
+ unsigned long recent = stream->recent;
+ unsigned long lastuid = 0;
+ short dirty = NIL;
+ short added = NIL;
+ short silent = stream->silent;
+ short uidwarn = T;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %lu to %lu!",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ lseek (LOCAL->fd,0,L_SET); /* rewind file */
+ /* read internal header */
+ read (LOCAL->fd,LOCAL->buf,HDRSIZE);
+ LOCAL->buf[HDRSIZE] = '\0'; /* tie off header */
+ c = LOCAL->buf[15]; /* save first character of last UID */
+ LOCAL->buf[15] = '\0';
+ /* parse UID validity */
+ stream->uid_validity = strtoul (LOCAL->buf + 7,NIL,16);
+ LOCAL->buf[15] = c; /* restore first character of last UID */
+ /* parse last UID */
+ i = strtoul (LOCAL->buf + 15,NIL,16);
+ stream->uid_last = stream->rdonly ? max (i,stream->uid_last) : i;
+ /* parse user flags */
+ for (i = 0, s = LOCAL->buf + 25;
+ (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s);
+ i++, s = t + 2) {
+ *t = '\0'; /* tie off flag */
+ if (!stream->user_flags[i] && (strlen (s) <= MAXUSERFLAG))
+ stream->user_flags[i] = cpystr (s);
+ }
+ LOCAL->ffuserflag = (int) i; /* first free user flag */
+
+ /* get current last flag updater PID */
+ i = (isxdigit (LOCAL->buf[HDRSIZE-10]) && isxdigit (LOCAL->buf[HDRSIZE-9]) &&
+ isxdigit (LOCAL->buf[HDRSIZE-8]) && isxdigit (LOCAL->buf[HDRSIZE-7]) &&
+ isxdigit (LOCAL->buf[HDRSIZE-6]) && isxdigit (LOCAL->buf[HDRSIZE-5]) &&
+ isxdigit (LOCAL->buf[HDRSIZE-4]) && isxdigit (LOCAL->buf[HDRSIZE-3]) &&
+ (LOCAL->buf[HDRSIZE-2] == '\015') && (LOCAL->buf[HDRSIZE-1] == '\012'))?
+ strtoul (LOCAL->buf + HDRSIZE - 8,NIL,16) : 0;
+ /* set flagcheck if lastpid changed */
+ if (LOCAL->lastpid && (LOCAL->lastpid != i)) LOCAL->flagcheck = T;
+ LOCAL->lastpid = i; /* set as last PID */
+ stream->silent = T; /* don't pass up exists events yet */
+ while (sbuf.st_size - curpos){/* while there is stuff to parse */
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,L_SET);
+ if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
+ sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size,
+ i ? strerror (errno) : "no data read");
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
+ if (!((s = strchr (LOCAL->buf,'\015')) && (s[1] == '\012'))) {
+ sprintf (tmp,"Unable to find CRLF at %lu in %lu bytes, text: %.80s",
+ (unsigned long) curpos,i,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ *s = '\0'; /* tie off header line */
+ i = (s + 2) - LOCAL->buf; /* note start of text offset */
+ if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
+ sprintf (tmp,"Unable to parse internal header at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ if (!(isxdigit (t[1]) && isxdigit (t[2]) && isxdigit (t[3]) &&
+ isxdigit (t[4]) && isxdigit (t[5]) && isxdigit (t[6]) &&
+ isxdigit (t[7]) && isxdigit (t[8]) && isxdigit (t[9]) &&
+ isxdigit (t[10]) && isxdigit (t[11]) && isxdigit (t[12]))) {
+ sprintf (tmp,"Unable to parse message flags at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ if ((t[13] != '-') || t[22] ||
+ !(isxdigit (t[14]) && isxdigit (t[15]) && isxdigit (t[16]) &&
+ isxdigit (t[17]) && isxdigit (t[18]) && isxdigit (t[19]) &&
+ isxdigit (t[20]) && isxdigit (t[21]))) {
+ sprintf (tmp,"Unable to parse message UID at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+
+ *s++ = '\0'; *t++ = '\0'; /* break up fields */
+ /* get message size */
+ if (!(j = strtoul (s,(char **) &x,10)) && (!(x && *x))) {
+ sprintf (tmp,"Unable to parse message size at %lu: %.80s,%.80s;%.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf,(char *) s,
+ (char *) t);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ /* make sure didn't run off end of file */
+ if (((off_t) (curpos + i + j)) > sbuf.st_size) {
+ sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
+ (unsigned long) curpos,(unsigned long) (curpos + i + j),
+ (unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ /* parse UID */
+ if ((m = strtoul (t+13,NIL,16)) &&
+ ((m <= lastuid) || (m > stream->uid_last))) {
+ if (uidwarn) {
+ sprintf (tmp,"Invalid UID %08lx in message %lu, rebuilding UIDs",
+ m,nmsgs+1);
+ MM_LOG (tmp,WARN);
+ uidwarn = NIL;
+ /* restart UID validity */
+ stream->uid_validity = time (0);
+ }
+ m = 0; /* lose this UID */
+ dirty = T; /* mark dirty, set new lastuid */
+ stream->uid_last = lastuid;
+ }
+
+ t[12] = '\0'; /* parse system flags */
+ if ((k = strtoul (t+8,NIL,16)) & fEXPUNGED) {
+ if (m) lastuid = m; /* expunge message, update last UID seen */
+ else { /* no UID assigned? */
+ lastuid = ++stream->uid_last;
+ dirty = T;
+ }
+ }
+ else { /* not expunged, swell the cache */
+ added = T; /* note that a new message was added */
+ mail_exists (stream,++nmsgs);
+ /* instantiate an elt for this message */
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ /* parse the date */
+ if (!mail_parse_date (elt,LOCAL->buf)) {
+ sprintf (tmp,"Unable to parse message date at %lu: %.80s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mbx_abort (stream);
+ return NIL;
+ }
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* and internal header size */
+ elt->private.special.text.size = i;
+ /* header size not known yet */
+ elt->private.msg.header.text.size = 0;
+ elt->rfc822_size = j; /* note message size */
+ /* calculate system flags */
+ if (k & fSEEN) elt->seen = T;
+ if (k & fDELETED) elt->deleted = T;
+ if (k & fFLAGGED) elt->flagged = T;
+ if (k & fANSWERED) elt->answered = T;
+ if (k & fDRAFT) elt->draft = T;
+ t[8] = '\0'; /* get user flags value */
+ elt->user_flags = strtoul (t,NIL,16);
+ /* UID already assigned? */
+ if (!(elt->private.uid = m) || !(k & fOLD)) {
+ elt->recent = T; /* no, mark as recent */
+ ++recent; /* count up a new recent message */
+ dirty = T; /* and must rewrite header */
+ /* assign new UID */
+ if (!elt->private.uid) elt->private.uid = ++stream->uid_last;
+ mbx_update_status (stream,elt->msgno,NIL);
+ }
+ /* update last parsed UID */
+ lastuid = elt->private.uid;
+ }
+ curpos += i + j; /* update position */
+ }
+
+ if (dirty && !stream->rdonly){/* update header */
+ mbx_update_header (stream);
+ fsync (LOCAL->fd); /* make sure all the UID updates take */
+ }
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
+ LOCAL->filetime = sbuf.st_mtime;
+ if (added && !stream->rdonly){/* make sure atime updated */
+ time_t tp[2];
+ tp[0] = time (0);
+ tp[1] = LOCAL->filetime;
+ utime (stream->mailbox,tp);
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return LONGT; /* return the winnage */
+}
+
+/* MBX get cache element with status updating from file
+ * Accepts: MAIL stream
+ * message number
+ * expunge OK flag
+ * Returns: cache element
+ */
+
+MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok)
+{
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
+ old.answered = elt->answered; old.draft = elt->draft;
+ old.user_flags = elt->user_flags;
+ /* get new flags */
+ if (mbx_read_flags (stream,elt) && expok) {
+ mail_expunged (stream,elt->msgno);
+ return NIL; /* return this message was expunged */
+ }
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
+ (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
+ MM_FLAGS (stream,msgno); /* let top level know */
+ return elt;
+}
+
+/* MBX read flags from file
+ * Accepts: MAIL stream
+ * cache element
+ * Returns: non-NIL if message expunged
+ */
+
+unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ unsigned long i;
+ struct stat sbuf;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* make sure file size is good */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag read!",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ fatal (LOCAL->buf);
+ }
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 24,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,14) < 0) {
+ sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) {
+ LOCAL->buf[14] = '\0'; /* tie off buffer for error message */
+ sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s",
+ elt->msgno,elt->private.special.offset,
+ elt->private.special.text.size,(char *) LOCAL->buf);
+ fatal (LOCAL->buf+50);
+ }
+ LOCAL->buf[13] = '\0'; /* tie off buffer */
+ /* calculate system flags */
+ i = strtoul (LOCAL->buf+9,NIL,16);
+ elt->seen = i & fSEEN ? T : NIL;
+ elt->deleted = i & fDELETED ? T : NIL;
+ elt->flagged = i & fFLAGGED ? T : NIL;
+ elt->answered = i & fANSWERED ? T : NIL;
+ elt->draft = i & fDRAFT ? T : NIL;
+ LOCAL->expunged |= i & fEXPUNGED ? T : NIL;
+ LOCAL->buf[9] = '\0'; /* tie off flags */
+ /* get user flags value */
+ elt->user_flags = strtoul (LOCAL->buf+1,NIL,16);
+ elt->valid = T; /* have valid flags now */
+ return i & fEXPUNGED;
+}
+
+/* MBX update header
+ * Accepts: MAIL stream
+ */
+
+#ifndef CYGKLUDGEOFFSET
+#define CYGKLUDGEOFFSET 0
+#endif
+
+void mbx_update_header (MAILSTREAM *stream)
+{
+ int i;
+ char *s = LOCAL->buf;
+ memset (s,'\0',HDRSIZE); /* initialize header */
+ sprintf (s,"*mbx*\015\012%08lx%08lx\015\012",
+ stream->uid_validity,stream->uid_last);
+ for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; ++i)
+ sprintf (s += strlen (s),"%s\015\012",stream->user_flags[i]);
+ LOCAL->ffuserflag = i; /* first free user flag */
+ /* can we create more user flags? */
+ stream->kwd_create = (i < NUSERFLAGS) ? T : NIL;
+ /* write reserved lines */
+ while (i++ < NUSERFLAGS) strcat (s,"\015\012");
+ sprintf (LOCAL->buf + HDRSIZE - 10,"%08lx\015\012",LOCAL->lastpid);
+ while (T) { /* rewind file */
+ lseek (LOCAL->fd,CYGKLUDGEOFFSET,L_SET);
+ /* write new header */
+ if (write (LOCAL->fd,LOCAL->buf + CYGKLUDGEOFFSET,
+ HDRSIZE - CYGKLUDGEOFFSET) > 0) break;
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+}
+
+/* MBX update status string
+ * Accepts: MAIL stream
+ * message number
+ * flags
+ */
+
+void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ /* readonly */
+ if (stream->rdonly || !elt->valid) mbx_read_flags (stream,elt);
+ else { /* readwrite */
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* make sure file size is good */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag update!",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ fatal (LOCAL->buf);
+ }
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 24,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,14) < 0) {
+ sprintf (LOCAL->buf,"Unable to read old status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) {
+ LOCAL->buf[14] = '\0'; /* tie off buffer for error message */
+ sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s",
+ elt->msgno,elt->private.special.offset,
+ elt->private.special.text.size,(char *) LOCAL->buf);
+ fatal (LOCAL->buf+50);
+ }
+ /* print new flag string */
+ sprintf (LOCAL->buf,"%08lx%04x-%08lx",elt->user_flags,(unsigned)
+ (((elt->deleted && flags) ?
+ fEXPUNGED : (strtoul (LOCAL->buf+9,NIL,16)) & fEXPUNGED) +
+ (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft) + fOLD),elt->private.uid);
+ while (T) { /* get to that place in the file */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 23,L_SET);
+ /* write new flags and UID */
+ if (write (LOCAL->fd,LOCAL->buf,21) > 0) break;
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ }
+}
+
+/* MBX locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * pointer to possible returned header
+ * Returns: position of header in file
+ */
+
+#define HDRBUFLEN 16384 /* good enough for most headers */
+#define SLOP 4 /* CR LF CR LF */
+
+unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size,char **hdr)
+{
+ unsigned long siz,done;
+ long i;
+ unsigned char *s,*t,*te;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ unsigned long ret = elt->private.special.offset +
+ elt->private.special.text.size;
+ if (hdr) *hdr = NIL; /* assume no header returned */
+ /* is header size known? */
+ if (*size = elt->private.msg.header.text.size) return ret;
+ /* paranoia check */
+ if (LOCAL->buflen < (HDRBUFLEN + SLOP))
+ fatal ("LOCAL->buf smaller than HDRBUFLEN");
+ lseek (LOCAL->fd,ret,L_SET); /* get to header position */
+ /* read HDRBUFLEN chunks with 4 byte slop */
+ for (done = siz = 0, s = LOCAL->buf;
+ (i = min ((long) (elt->rfc822_size - done),(long) HDRBUFLEN)) &&
+ (read (LOCAL->fd,s,i) == i);
+ done += i, siz += (t - LOCAL->buf) - SLOP, s = LOCAL->buf + SLOP) {
+ te = (t = s + i) - 12; /* calculate end of fast scan */
+ /* fast scan for CR */
+ for (s = LOCAL->buf; s < te;)
+ if (((*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
+ (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
+ (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
+ (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015')) &&
+ (*s == '\012') && (*++s == '\015') && (*++s == '\012')) {
+ *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf);
+ if (hdr) *hdr = LOCAL->buf;
+ return ret;
+ }
+ for (te = t - 3; (s < te);) /* final character-at-a-time scan */
+ if ((*s++ == '\015') && (*s == '\012') && (*++s == '\015') &&
+ (*++s == '\012')) {
+ *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf);
+ if (hdr) *hdr = LOCAL->buf;
+ return ret;
+ }
+ if (i <= SLOP) break; /* end of data */
+ /* slide over last 4 bytes */
+ memmove (LOCAL->buf,t - SLOP,SLOP);
+ hdr = NIL; /* can't return header this way */
+ }
+ /* not found: header consumes entire message */
+ elt->private.msg.header.text.size = *size = elt->rfc822_size;
+ if (hdr) *hdr = LOCAL->buf; /* possibly return header too */
+ return ret;
+}
+
+/* MBX mail rewrite mailbox
+ * Accepts: MAIL stream
+ * pointer to return reclaimed size
+ * flags (0 = no expunge, 1 = expunge deleted, -1 = expunge sequence)
+ * Returns: number of expunged messages
+ */
+
+unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed,
+ long flags)
+{
+ time_t tp[2];
+ struct stat sbuf;
+ off_t pos,ppos;
+ int ld;
+ unsigned long i,j,k,m,delta;
+ unsigned long n = *reclaimed = 0;
+ unsigned long recent = 0;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* The cretins who designed flock() created a window of vulnerability in
+ * upgrading locks from shared to exclusive or downgrading from exclusive
+ * to shared. Rather than maintain the lock at shared status at a minimum,
+ * flock() actually *releases* the former lock. Obviously they never talked
+ * to any database guys. Fortunately, we have the parse/append permission
+ * lock. If we require this lock before going exclusive on the mailbox,
+ * another process can not sneak in and steal the exclusive mailbox lock on
+ * us, because it will block on trying to get parse/append permission first.
+ */
+ /* get parse/append permission */
+ if ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) < 0) {
+ MM_LOG ("Unable to lock mailbox for rewrite",ERROR);
+ return 0;
+ }
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime && !LOCAL->flagcheck &&
+ (LOCAL->filetime < sbuf.st_mtime)) LOCAL->flagcheck = T;
+ if (!mbx_parse (stream)) { /* make sure see any newly-arrived messages */
+ unlockfd (ld,lock); /* failed?? */
+ return 0;
+ }
+ if (LOCAL->flagcheck) { /* sweep flags if need flagcheck */
+ LOCAL->filetime = sbuf.st_mtime;
+ for (i = 1; i <= stream->nmsgs; ++i) mbx_elt (stream,i,NIL);
+ LOCAL->flagcheck = NIL;
+ }
+
+ /* get exclusive access */
+ if (!flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
+ MM_CRITICAL (stream); /* go critical */
+ for (i = 1,delta = 0,pos = ppos = HDRSIZE; i <= stream->nmsgs; ) {
+ /* note if message not at predicted location */
+ if (m = (elt = mbx_elt (stream,i,NIL))->private.special.offset - ppos) {
+ ppos = elt->private.special.offset;
+ *reclaimed += m; /* note reclaimed message space */
+ delta += m; /* and as expunge delta */
+ }
+ /* number of bytes to smash or preserve */
+ ppos += (k = elt->private.special.text.size + elt->rfc822_size);
+ /* if need to expunge this message*/
+ if (flags && elt->deleted && ((flags > 0) || elt->sequence)) {
+ delta += k; /* number of bytes to delete */
+ mail_expunged(stream,i);/* notify upper levels */
+ n++; /* count up one more expunged message */
+ }
+ else { /* preserved message */
+ i++; /* count this message */
+ if (elt->recent) ++recent;
+ if (delta) { /* moved, note first byte to preserve */
+ j = elt->private.special.offset;
+ do { /* read from source position */
+ m = min (k,LOCAL->buflen);
+ lseek (LOCAL->fd,j,L_SET);
+ read (LOCAL->fd,LOCAL->buf,m);
+ pos = j - delta; /* write to destination position */
+ while (T) {
+ lseek (LOCAL->fd,pos,L_SET);
+ if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ pos += m; /* new position */
+ j += m; /* next chunk, perhaps */
+ } while (k -= m); /* until done */
+ /* note the new address of this text */
+ elt->private.special.offset -= delta;
+ }
+ /* preserved but no deleted messages yet */
+ else pos = elt->private.special.offset + k;
+ }
+ }
+ /* deltaed file size match position? */
+ if (m = (LOCAL->filesize -= delta) - pos) {
+ *reclaimed += m; /* probably an fEXPUNGED msg */
+ LOCAL->filesize = pos; /* set correct size */
+ }
+ /* truncate file after last message */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ fsync (LOCAL->fd); /* force disk update */
+ MM_NOCRITICAL (stream); /* release critical */
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH); /* allow sharers again */
+ (*bn) (BLOCK_NONE,NIL);
+ }
+
+ else { /* can't get exclusive */
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH); /* recover previous shared mailbox lock */
+ (*bn) (BLOCK_NONE,NIL);
+ /* do hide-expunge when shared */
+ if (flags) for (i = 1; i <= stream->nmsgs; ) {
+ if (elt = mbx_elt (stream,i,T)) {
+ /* make the message invisible */
+ if (elt->deleted && ((flags > 0) || elt->sequence)) {
+ mbx_update_status (stream,elt->msgno,LONGT);
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else {
+ i++; /* preserved message */
+ if (elt->recent) ++recent;
+ }
+ }
+ else n++; /* count up one more expunged message */
+ }
+ fsync (LOCAL->fd); /* force disk update */
+ }
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* reset atime to now */
+ utime (stream->mailbox,tp);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ return n; /* return number of expunged messages */
+}
+
+/* MBX mail lock for flag updating
+ * Accepts: stream
+ * Returns: T if successful, NIL if failure
+ */
+
+long mbx_flaglock (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ unsigned long i;
+ int ld;
+ char lock[MAILTMPLEN];
+ /* no-op if readonly or already locked */
+ if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld < 0)) {
+ /* lock now */
+ if ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) < 0) return NIL;
+ if (!LOCAL->flagcheck) { /* don't do this if flagcheck already needed */
+ if (LOCAL->filetime) { /* know previous time? */
+ fstat (LOCAL->fd,&sbuf);/* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->flagcheck = T;
+ LOCAL->filetime = 0; /* don't do this test for any other messages */
+ }
+ if (!mbx_parse (stream)) {/* parse mailbox */
+ unlockfd (ld,lock); /* shouldn't happen */
+ return NIL;
+ }
+ if (LOCAL->flagcheck) /* invalidate cache if flagcheck */
+ for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->valid = NIL;
+ }
+ LOCAL->ld = ld; /* copy to stream for subsequent calls */
+ memcpy (LOCAL->lock,lock,MAILTMPLEN);
+ }
+ return LONGT;
+}
diff --git a/imap/src/osdep/unix/mh.c b/imap/src/osdep/unix/mh.c
new file mode 100644
index 00000000..0226b7af
--- /dev/null
+++ b/imap/src/osdep/unix/mh.c
@@ -0,0 +1,1283 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MH mail routines
+ *
+ * Author(s): Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 23 February 1992
+ * Last Edited: 11 October 2007
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+
+/* Build parameters */
+
+#define MHINBOX "#mhinbox" /* corresponds to namespace in env_unix.c */
+#define MHINBOXDIR "inbox"
+#define MHPROFILE ".mh_profile"
+#define MHCOMMA ','
+#define MHSEQUENCE ".mh_sequence"
+#define MHSEQUENCES ".mh_sequences"
+#define MHPATH "Mail"
+
+
+/* mh_load_message() flags */
+
+#define MLM_HEADER 0x1 /* load message text */
+#define MLM_TEXT 0x2 /* load message text */
+
+/* MH I/O stream local data */
+
+typedef struct mh_local {
+ char *dir; /* spool directory name */
+ unsigned char buf[CHUNKSIZE]; /* temporary buffer */
+ unsigned long cachedtexts; /* total size of all cached texts */
+ time_t scantime; /* last time directory scanned */
+} MHLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MHLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *mh_valid (char *name);
+int mh_isvalid (char *name,char *tmp,long synonly);
+int mh_namevalid (char *name);
+char *mh_path (char *tmp);
+void *mh_parameters (long function,void *value);
+long mh_dirfmttest (char *name);
+void mh_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void mh_list (MAILSTREAM *stream,char *ref,char *pat);
+void mh_lsub (MAILSTREAM *stream,char *ref,char *pat);
+void mh_list_work (MAILSTREAM *stream,char *dir,char *pat,long level);
+long mh_subscribe (MAILSTREAM *stream,char *mailbox);
+long mh_unsubscribe (MAILSTREAM *stream,char *mailbox);
+long mh_create (MAILSTREAM *stream,char *mailbox);
+long mh_delete (MAILSTREAM *stream,char *mailbox);
+long mh_rename (MAILSTREAM *stream,char *old,char *newname);
+MAILSTREAM *mh_open (MAILSTREAM *stream);
+void mh_close (MAILSTREAM *stream,long options);
+void mh_fast (MAILSTREAM *stream,char *sequence,long flags);
+void mh_load_message (MAILSTREAM *stream,unsigned long msgno,long flags);
+char *mh_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags);
+long mh_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+long mh_ping (MAILSTREAM *stream);
+void mh_check (MAILSTREAM *stream);
+long mh_expunge (MAILSTREAM *stream,char *sequence,long options);
+long mh_copy (MAILSTREAM *stream,char *sequence,char *mailbox,
+ long options);
+long mh_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+int mh_select (struct direct *name);
+int mh_numsort (const void *d1,const void *d2);
+char *mh_file (char *dst,char *name);
+long mh_canonicalize (char *pattern,char *ref,char *pat);
+void mh_setdate (char *file,MESSAGECACHE *elt);
+
+/* MH mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mhdriver = {
+ "mh", /* driver name */
+ /* driver flags */
+ DR_MAIL|DR_LOCAL|DR_NOFAST|DR_NAMESPACE|DR_NOSTICKY|DR_DIRFMT,
+ (DRIVER *) NIL, /* next driver */
+ mh_valid, /* mailbox is valid for us */
+ mh_parameters, /* manipulate parameters */
+ mh_scan, /* scan mailboxes */
+ mh_list, /* find mailboxes */
+ mh_lsub, /* find subscribed mailboxes */
+ mh_subscribe, /* subscribe to mailbox */
+ mh_unsubscribe, /* unsubscribe from mailbox */
+ mh_create, /* create mailbox */
+ mh_delete, /* delete mailbox */
+ mh_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ mh_open, /* open mailbox */
+ mh_close, /* close mailbox */
+ mh_fast, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mh_header, /* fetch message header */
+ mh_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mh_ping, /* ping mailbox to see if still alive */
+ mh_check, /* check for new messages */
+ mh_expunge, /* expunge deleted messages */
+ mh_copy, /* copy messages to another mailbox */
+ mh_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mhproto = {&mhdriver};
+
+
+static char *mh_profile = NIL; /* holds MH profile */
+static char *mh_pathname = NIL; /* holds MH path name */
+static long mh_once = 0; /* already snarled once */
+static long mh_allow_inbox =NIL;/* allow INBOX as well as MHINBOX */
+
+/* MH mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mh_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return mh_isvalid (name,tmp,T) ? &mhdriver : NIL;
+}
+
+
+/* MH mail test for valid mailbox
+ * Accepts: mailbox name
+ * temporary buffer to use
+ * syntax only test flag
+ * Returns: T if valid, NIL otherwise
+ */
+
+int mh_isvalid (char *name,char *tmp,long synonly)
+{
+ struct stat sbuf;
+ char *s,*t,altname[MAILTMPLEN];
+ unsigned long i;
+ int ret = NIL;
+ errno = NIL; /* zap any error condition */
+ /* mh name? */
+ if ((mh_allow_inbox && !compare_cstring (name,"INBOX")) ||
+ !compare_cstring (name,MHINBOX) ||
+ ((name[0] == '#') && ((name[1] == 'm') || (name[1] == 'M')) &&
+ ((name[2] == 'h') || (name[2] == 'H')) && (name[3] == '/') && name[4])){
+ if (mh_path (tmp)) /* validate name if INBOX or not synonly */
+ ret = (synonly && compare_cstring (name,"INBOX")) ?
+ T : ((stat (mh_file (tmp,name),&sbuf) == 0) &&
+ (sbuf.st_mode & S_IFMT) == S_IFDIR);
+ else if (!mh_once++) { /* only report error once */
+ sprintf (tmp,"%.900s not found, mh format names disabled",mh_profile);
+ mm_log (tmp,WARN);
+ }
+ }
+ /* see if non-NS name within mh hierarchy */
+ else if ((name[0] != '#') && (s = mh_path (tmp)) && (i = strlen (s)) &&
+ (t = mailboxfile (tmp,name)) && !strncmp (t,s,i) &&
+ (tmp[i] == '/') && tmp[i+1]) {
+ sprintf (altname,"#mh%.900s",tmp+i);
+ /* can't do synonly here! */
+ ret = mh_isvalid (altname,tmp,NIL);
+ }
+ else errno = EINVAL; /* bogus name */
+ return ret;
+}
+
+/* MH mail test for valid mailbox
+ * Accepts: mailbox name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int mh_namevalid (char *name)
+{
+ char *s;
+ if (name[0] == '#' && (name[1] == 'm' || name[1] == 'M') &&
+ (name[2] == 'h' || name[2] == 'H') && name[3] == '/')
+ for (s = name; s && *s;) { /* make sure no all-digit nodes */
+ if (isdigit (*s)) s++; /* digit, check this node further... */
+ else if (*s == '/') break;/* all digit node, barf */
+ /* non-digit, skip to next node or return */
+ else if (!((s = strchr (s+1,'/')) && *++s)) return T;
+ }
+ return NIL; /* all numeric or empty node */
+}
+
+/* Return MH path
+ * Accepts: temporary buffer
+ * Returns: MH path or NIL if MH disabled
+ */
+
+char *mh_path (char *tmp)
+{
+ char *s,*t,*v,*r;
+ int fd;
+ struct stat sbuf;
+ if (!mh_profile) { /* build mh_profile and mh_pathname now */
+ sprintf (tmp,"%s/%s",myhomedir (),MHPROFILE);
+ if ((fd = open (mh_profile = cpystr (tmp),O_RDONLY,NIL)) >= 0) {
+ fstat (fd,&sbuf); /* yes, get size and read file */
+ read (fd,(t = (char *) fs_get (sbuf.st_size + 1)),sbuf.st_size);
+ close (fd); /* don't need the file any more */
+ t[sbuf.st_size] = '\0'; /* tie it off */
+ /* parse profile file */
+ for (s = strtok_r (t,"\r\n",&r); s && *s; s = strtok_r (NIL,"\r\n",&r)) {
+ /* found space in line? */
+ if (v = strpbrk (s," \t")) {
+ *v++ = '\0'; /* tie off, is keyword "Path:"? */
+ if (!compare_cstring (s,"Path:")) {
+ /* skip whitespace */
+ while ((*v == ' ') || (*v == '\t')) ++v;
+ /* absolute path? */
+ if (*v == '/') s = v;
+ else sprintf (s = tmp,"%s/%s",myhomedir (),v);
+ /* copy name */
+ mh_pathname = cpystr (s);
+ break; /* don't need to look at rest of file */
+ }
+ }
+ }
+ fs_give ((void **) &t); /* flush profile text */
+ if (!mh_pathname) { /* default path if not in the profile */
+ sprintf (tmp,"%s/%s",myhomedir (),MHPATH);
+ mh_pathname = cpystr (tmp);
+ }
+ }
+ }
+ return mh_pathname;
+}
+
+/* MH manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mh_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = mh_file ((char *) value,"INBOX");
+ break;
+ case GET_DIRFMTTEST:
+ ret = (void *) mh_dirfmttest;
+ break;
+ case SET_MHPROFILE:
+ if (mh_profile) fs_give ((void **) &mh_profile);
+ mh_profile = cpystr ((char *) value);
+ case GET_MHPROFILE:
+ ret = (void *) mh_profile;
+ break;
+ case SET_MHPATH:
+ if (mh_pathname) fs_give ((void **) &mh_pathname);
+ mh_pathname = cpystr ((char *) value);
+ case GET_MHPATH:
+ ret = (void *) mh_pathname;
+ break;
+ case SET_MHALLOWINBOX:
+ mh_allow_inbox = value ? T : NIL;
+ case GET_MHALLOWINBOX:
+ ret = (void *) (mh_allow_inbox ? VOIDT : NIL);
+ }
+ return ret;
+}
+
+
+/* MH test for directory format internal node
+ * Accepts: candidate node name
+ * Returns: T if internal name, NIL otherwise
+ */
+
+long mh_dirfmttest (char *s)
+{
+ int c;
+ /* sequence(s) file is an internal name */
+ if (strcmp (s,MHSEQUENCE) && strcmp (s,MHSEQUENCES)) {
+ if (*s == MHCOMMA) ++s; /* else comma + all numeric name */
+ /* success if all-numeric */
+ while (c = *s++) if (!isdigit (c)) return NIL;
+ }
+ return LONGT;
+}
+
+/* MH scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mh_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ char *s,test[MAILTMPLEN],file[MAILTMPLEN];
+ long i = 0;
+ if (!pat || !*pat) { /* empty pattern? */
+ if (mh_canonicalize (test,ref,"*")) {
+ /* tie off name at root */
+ if (s = strchr (test,'/')) *++s = '\0';
+ else test[0] = '\0';
+ mm_list (stream,'/',test,LATT_NOSELECT);
+ }
+ }
+ /* get canonical form of name */
+ else if (mh_canonicalize (test,ref,pat)) {
+ if (contents) { /* maybe I'll implement this someday */
+ mm_log ("Scan not valid for mh mailboxes",ERROR);
+ return;
+ }
+ if (test[3] == '/') { /* looking down levels? */
+ /* yes, found any wildcards? */
+ if (s = strpbrk (test,"%*")) {
+ /* yes, copy name up to that point */
+ strncpy (file,test+4,i = s - (test+4));
+ file[i] = '\0'; /* tie off */
+ }
+ else strcpy (file,test+4);/* use just that name then */
+ /* find directory name */
+ if (s = strrchr (file,'/')) {
+ *s = '\0'; /* found, tie off at that point */
+ s = file;
+ }
+ /* do the work */
+ mh_list_work (stream,s,test,0);
+ }
+ /* always an INBOX */
+ if (!compare_cstring (test,MHINBOX))
+ mm_list (stream,NIL,MHINBOX,LATT_NOINFERIORS);
+ }
+}
+
+/* MH list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mh_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ mh_scan (stream,ref,pat,NIL);
+}
+
+
+/* MH list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mh_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ void *sdb = NIL;
+ char *s,test[MAILTMPLEN];
+ /* get canonical form of name */
+ if (mh_canonicalize (test,ref,pat) && (s = sm_read (&sdb))) {
+ do if (pmatch_full (s,test,'/')) mm_lsub (stream,'/',s,NIL);
+ while (s = sm_read (&sdb)); /* until no more subscriptions */
+ }
+}
+
+/* MH list mailboxes worker routine
+ * Accepts: mail stream
+ * directory name to search
+ * search pattern
+ * search level
+ */
+
+void mh_list_work (MAILSTREAM *stream,char *dir,char *pat,long level)
+{
+ DIR *dp;
+ struct direct *d;
+ struct stat sbuf;
+ char *cp,*np,curdir[MAILTMPLEN],name[MAILTMPLEN];
+ /* build MH name to search */
+ if (dir) sprintf (name,"#mh/%s/",dir);
+ else strcpy (name,"#mh/");
+ /* make directory name, punt if bogus */
+ if (!mh_file (curdir,name)) return;
+ cp = curdir + strlen (curdir);/* end of directory name */
+ np = name + strlen (name); /* end of MH name */
+ if (dp = opendir (curdir)) { /* open directory */
+ while (d = readdir (dp)) /* scan, ignore . and numeric names */
+ if ((d->d_name[0] != '.') && !mh_select (d)) {
+ strcpy (cp,d->d_name); /* make directory name */
+ if (!stat (curdir,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
+ strcpy (np,d->d_name);/* make mh name of directory name */
+ /* yes, an MH name if full match */
+ if (pmatch_full (name,pat,'/')) mm_list (stream,'/',name,NIL);
+ /* check if should recurse */
+ if (dmatch (name,pat,'/') &&
+ (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL)))
+ mh_list_work (stream,name+4,pat,level+1);
+ }
+ }
+ closedir (dp); /* all done, flush directory */
+ }
+}
+
+/* MH mail subscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to add to subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long mh_subscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return sm_subscribe (mailbox);
+}
+
+
+/* MH mail unsubscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to delete from subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long mh_unsubscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return sm_unsubscribe (mailbox);
+}
+
+/* MH mail create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mh_create (MAILSTREAM *stream,char *mailbox)
+{
+ char tmp[MAILTMPLEN];
+ if (!mh_namevalid (mailbox)) /* validate name */
+ sprintf (tmp,"Can't create mailbox %.80s: invalid MH-format name",mailbox);
+ /* must not already exist */
+ else if (mh_isvalid (mailbox,tmp,NIL))
+ sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox);
+ else if (!mh_path (tmp)) return NIL;
+ /* try to make it */
+ else if (!(mh_file (tmp,mailbox) &&
+ dummy_create_path (stream,strcat (tmp,"/"),
+ get_dir_protection (mailbox))))
+ sprintf (tmp,"Can't create mailbox %.80s: %s",mailbox,strerror (errno));
+ else return LONGT; /* success */
+ mm_log (tmp,ERROR);
+ return NIL;
+}
+
+/* MH mail delete mailbox
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mh_delete (MAILSTREAM *stream,char *mailbox)
+{
+ DIR *dirp;
+ struct direct *d;
+ int i;
+ char tmp[MAILTMPLEN];
+ /* is mailbox valid? */
+ if (!mh_isvalid (mailbox,tmp,NIL)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get name of directory */
+ i = strlen (mh_file (tmp,mailbox));
+ if (dirp = opendir (tmp)) { /* open directory */
+ tmp[i++] = '/'; /* now apply trailing delimiter */
+ /* massacre all mh owned files */
+ while (d = readdir (dirp)) if (mh_dirfmttest (d->d_name)) {
+ strcpy (tmp + i,d->d_name);
+ unlink (tmp); /* sayonara */
+ }
+ closedir (dirp); /* flush directory */
+ }
+ /* try to remove the directory */
+ if (rmdir (mh_file (tmp,mailbox))) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",mailbox,strerror (errno));
+ mm_log (tmp,WARN);
+ }
+ return T; /* return success */
+}
+
+/* MH mail rename mailbox
+ * Accepts: MH mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long mh_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN];
+ struct stat sbuf;
+ /* old mailbox name must be valid */
+ if (!mh_isvalid (old,tmp,NIL))
+ sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old);
+ else if (!mh_namevalid (newname))
+ sprintf (tmp,"Can't rename to mailbox %.80s: invalid MH-format name",
+ newname);
+ /* new mailbox name must not be valid */
+ else if (mh_isvalid (newname,tmp,NIL))
+ sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists",
+ newname);
+ /* success if can rename the directory */
+ else { /* found superior to destination name? */
+ if (s = strrchr (mh_file (tmp1,newname),'/')) {
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp1,get_dir_protection (newname)))
+ return NIL;
+ *s = c; /* restore full name */
+ }
+ if (!rename (mh_file (tmp,old),tmp1)) return T;
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",
+ old,newname,strerror (errno));
+ }
+ mm_log (tmp,ERROR); /* something failed */
+ return NIL;
+}
+
+/* MH mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mh_open (MAILSTREAM *stream)
+{
+ char tmp[MAILTMPLEN];
+ if (!stream) return &mhproto; /* return prototype for OP_PROTOTYPE call */
+ if (stream->local) fatal ("mh recycle stream");
+ stream->local = fs_get (sizeof (MHLOCAL));
+ /* INBOXness is one of the following:
+ * #mhinbox (case-independent)
+ * #mh/inbox (mh is case-independent, inbox is case-dependent)
+ * INBOX (case-independent
+ */
+ stream->inbox = /* note if an INBOX or not */
+ (!compare_cstring (stream->mailbox,MHINBOX) ||
+ ((stream->mailbox[0] == '#') &&
+ ((stream->mailbox[1] == 'm') || (stream->mailbox[1] == 'M')) &&
+ ((stream->mailbox[2] == 'h') || (stream->mailbox[2] == 'H')) &&
+ (stream->mailbox[3] == '/') && !strcmp (stream->mailbox+4,MHINBOXDIR)) ||
+ !compare_cstring (stream->mailbox,"INBOX")) ? T : NIL;
+ mh_file (tmp,stream->mailbox);/* get directory name */
+ LOCAL->dir = cpystr (tmp); /* copy directory name for later */
+ LOCAL->scantime = 0; /* not scanned yet */
+ LOCAL->cachedtexts = 0; /* no cached texts */
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (!mh_ping (stream)) return NIL;
+ if (!(stream->nmsgs || stream->silent))
+ mm_log ("Mailbox is empty",(long) NIL);
+ return stream; /* return stream to caller */
+}
+
+/* MH mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mh_close (MAILSTREAM *stream,long options)
+{
+ if (LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ if (options & CL_EXPUNGE) mh_expunge (stream,NIL,NIL);
+ if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ stream->silent = silent; /* reset silent state */
+ }
+}
+
+
+/* MH mail fetch fast information
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ */
+
+void mh_fast (MAILSTREAM *stream,char *sequence,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned long i;
+ /* set up metadata for all messages */
+ if (stream && LOCAL && ((flags & FT_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence &&
+ !(elt->day && elt->rfc822_size)) mh_load_message (stream,i,NIL);
+}
+
+/* MH load message into cache
+ * Accepts: MAIL stream
+ * message #
+ * option flags
+ */
+
+void mh_load_message (MAILSTREAM *stream,unsigned long msgno,long flags)
+{
+ unsigned long i,j,nlseen;
+ int fd;
+ unsigned char c,*t;
+ struct stat sbuf;
+ MESSAGECACHE *elt;
+ FDDATA d;
+ STRING bs;
+ elt = mail_elt (stream,msgno);/* get elt */
+ /* build message file name */
+ sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
+ /* anything we need not currently cached? */
+ if ((!elt->day || !elt->rfc822_size ||
+ ((flags & MLM_HEADER) && !elt->private.msg.header.text.data) ||
+ ((flags & MLM_TEXT) && !elt->private.msg.text.text.data)) &&
+ ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0)) {
+ fstat (fd,&sbuf); /* get file metadata */
+ d.fd = fd; /* set up file descriptor */
+ d.pos = 0; /* start of file */
+ d.chunk = LOCAL->buf;
+ d.chunksize = CHUNKSIZE;
+ INIT (&bs,fd_string,&d,sbuf.st_size);
+ if (!elt->day) { /* set internaldate to file date */
+ struct tm *tm = gmtime (&sbuf.st_mtime);
+ elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
+ elt->year = tm->tm_year + 1900 - BASEYEAR;
+ elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
+ elt->seconds = tm->tm_sec;
+ elt->zhours = 0; elt->zminutes = 0;
+ }
+
+ if (!elt->rfc822_size) { /* know message size yet? */
+ for (i = 0, j = SIZE (&bs), nlseen = 0; j--; ) switch (SNX (&bs)) {
+ case '\015': /* unlikely carriage return */
+ if (!j || (CHR (&bs) != '\012')) {
+ i++; /* ugh, raw CR */
+ nlseen = NIL;
+ break;
+ }
+ SNX (&bs); /* eat the line feed, drop in */
+ --j;
+ case '\012': /* line feed? */
+ i += 2; /* count a CRLF */
+ /* header size known yet? */
+ if (!elt->private.msg.header.text.size && nlseen) {
+ /* note position in file */
+ elt->private.special.text.size = GETPOS (&bs);
+ /* and CRLF-adjusted size */
+ elt->private.msg.header.text.size = i;
+ }
+ nlseen = T; /* note newline seen */
+ break;
+ default: /* ordinary chararacter */
+ i++;
+ nlseen = NIL;
+ break;
+ }
+ SETPOS (&bs,0); /* restore old position */
+ elt->rfc822_size = i; /* note that we have size now */
+ /* header is entire message if no delimiter */
+ if (!elt->private.msg.header.text.size)
+ elt->private.msg.header.text.size = elt->rfc822_size;
+ /* text is remainder of message */
+ elt->private.msg.text.text.size =
+ elt->rfc822_size - elt->private.msg.header.text.size;
+ }
+ /* need to load cache with message data? */
+ if (((flags & MLM_HEADER) && !elt->private.msg.header.text.data) ||
+ ((flags & MLM_TEXT) && !elt->private.msg.text.text.data)) {
+ /* purge cache if too big */
+ if (LOCAL->cachedtexts > max (stream->nmsgs * 4096,2097152)) {
+ /* just can't keep that much */
+ mail_gc (stream,GC_TEXTS);
+ LOCAL->cachedtexts = 0;
+ }
+
+ if ((flags & MLM_HEADER) && !elt->private.msg.header.text.data) {
+ t = elt->private.msg.header.text.data =
+ (unsigned char *) fs_get (elt->private.msg.header.text.size + 1);
+ LOCAL->cachedtexts += elt->private.msg.header.text.size;
+ /* read in message header */
+ for (i = 0; i < elt->private.msg.header.text.size; i++)
+ switch (c = SNX (&bs)) {
+ case '\015': /* unlikely carriage return */
+ *t++ = c;
+ if ((CHR (&bs) == '\012')) {
+ *t++ = SNX (&bs);
+ i++;
+ }
+ break;
+ case '\012': /* line feed? */
+ *t++ = '\015';
+ i++;
+ default:
+ *t++ = c;
+ break;
+ }
+ *t = '\0'; /* tie off string */
+ if ((t - elt->private.msg.header.text.data) !=
+ elt->private.msg.header.text.size) fatal ("mh hdr size mismatch");
+ }
+ if ((flags & MLM_TEXT) && !elt->private.msg.text.text.data) {
+ t = elt->private.msg.text.text.data =
+ (unsigned char *) fs_get (elt->private.msg.text.text.size + 1);
+ SETPOS (&bs,elt->private.special.text.size);
+ LOCAL->cachedtexts += elt->private.msg.text.text.size;
+ /* read in message text */
+ for (i = 0; i < elt->private.msg.text.text.size; i++)
+ switch (c = SNX (&bs)) {
+ case '\015': /* unlikely carriage return */
+ *t++ = c;
+ if ((CHR (&bs) == '\012')) {
+ *t++ = SNX (&bs);
+ i++;
+ }
+ break;
+ case '\012': /* line feed? */
+ *t++ = '\015';
+ i++;
+ default:
+ *t++ = c;
+ break;
+ }
+ *t = '\0'; /* tie off string */
+ if ((t - elt->private.msg.text.text.data) !=
+ elt->private.msg.text.text.size) fatal ("mh txt size mismatch");
+ }
+ }
+ close (fd); /* flush message file */
+ }
+}
+
+/* MH mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mh_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ MESSAGECACHE *elt;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get elt */
+ if (!elt->private.msg.header.text.data)
+ mh_load_message (stream,msgno,MLM_HEADER);
+ *length = elt->private.msg.header.text.size;
+ return (char *) elt->private.msg.header.text.data;
+}
+
+
+/* MH mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mh_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);/* get elt */
+ /* snarf message if don't have it yet */
+ if (!elt->private.msg.text.text.data) {
+ mh_load_message (stream,msgno,MLM_TEXT);
+ if (!elt->private.msg.text.text.data) return NIL;
+ }
+ if (!(flags & FT_PEEK)) { /* mark as seen */
+ mail_elt (stream,msgno)->seen = T;
+ mm_flags (stream,msgno);
+ }
+ INIT (bs,mail_string,elt->private.msg.text.text.data,
+ elt->private.msg.text.text.size);
+ return T;
+}
+
+/* MH mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long mh_ping (MAILSTREAM *stream)
+{
+ MAILSTREAM *sysibx = NIL;
+ MESSAGECACHE *elt,*selt;
+ struct stat sbuf;
+ char *s,tmp[MAILTMPLEN];
+ int fd;
+ unsigned long i,j,r;
+ unsigned long old = stream->uid_last;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ int silent = stream->silent;
+ if (stat (LOCAL->dir,&sbuf)) {/* directory exists? */
+ if (stream->inbox && /* no, create if INBOX */
+ dummy_create_path (stream,strcat (mh_file (tmp,MHINBOX),"/"),
+ get_dir_protection ("INBOX"))) return T;
+ sprintf (tmp,"Can't open mailbox %.80s: no such mailbox",stream->mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ stream->silent = T; /* don't pass up mm_exists() events yet */
+ if (sbuf.st_ctime != LOCAL->scantime) {
+ struct direct **names = NIL;
+ long nfiles = scandir (LOCAL->dir,&names,mh_select,mh_numsort);
+ if (nfiles < 0) nfiles = 0; /* in case error */
+ /* note scanned now */
+ LOCAL->scantime = sbuf.st_ctime;
+ /* scan directory */
+ for (i = 0; i < nfiles; ++i) {
+ /* if newly seen, add to list */
+ if ((j = atoi (names[i]->d_name)) > old) {
+ mail_exists (stream,++nmsgs);
+ stream->uid_last = (elt = mail_elt (stream,nmsgs))->private.uid = j;
+ elt->valid = T; /* note valid flags */
+ if (old) { /* other than the first pass? */
+ elt->recent = T; /* yup, mark as recent */
+ recent++; /* bump recent count */
+ }
+ else { /* see if already read */
+ sprintf (tmp,"%s/%s",LOCAL->dir,names[i]->d_name);
+ if (!stat (tmp,&sbuf) && (sbuf.st_atime > sbuf.st_mtime))
+ elt->seen = T;
+ }
+ }
+ fs_give ((void **) &names[i]);
+ }
+ /* free directory */
+ if (s = (void *) names) fs_give ((void **) &s);
+ }
+
+ /* if INBOX, snarf from system INBOX */
+ if (stream->inbox && strcmp (sysinbox (),stream->mailbox)) {
+ old = stream->uid_last;
+ mm_critical (stream); /* go critical */
+ /* see if anything in system inbox */
+ if (!stat (sysinbox (),&sbuf) && sbuf.st_size &&
+ (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
+ !sysibx->rdonly && (r = sysibx->nmsgs)) {
+ for (i = 1; i <= r; ++i) {/* for each message in sysinbox mailbox */
+ /* build file name we will use */
+ sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,++old);
+ /* snarf message from Berkeley mailbox */
+ selt = mail_elt (sysibx,i);
+ if (((fd = open (LOCAL->buf,O_WRONLY|O_CREAT|O_EXCL,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL)))
+ >= 0) &&
+ (s = mail_fetchheader_full (sysibx,i,NIL,&j,FT_INTERNAL)) &&
+ (write (fd,s,j) == j) &&
+ (s = mail_fetchtext_full (sysibx,i,&j,FT_INTERNAL|FT_PEEK)) &&
+ (write (fd,s,j) == j) && !fsync (fd) && !close (fd)) {
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ stream->uid_last = /* create new elt, note its file number */
+ (elt = mail_elt (stream,nmsgs))->private.uid = old;
+ recent++; /* bump recent count */
+ /* set up initial flags and date */
+ elt->valid = elt->recent = T;
+ elt->seen = selt->seen;
+ elt->deleted = selt->deleted;
+ elt->flagged = selt->flagged;
+ elt->answered = selt->answered;
+ elt->draft = selt->draft;
+ elt->day = selt->day;elt->month = selt->month;elt->year = selt->year;
+ elt->hours = selt->hours;elt->minutes = selt->minutes;
+ elt->seconds = selt->seconds;
+ elt->zhours = selt->zhours; elt->zminutes = selt->zminutes;
+ elt->zoccident = selt->zoccident;
+ mh_setdate (LOCAL->buf,elt);
+ sprintf (tmp,"%lu",i);/* delete it from the sysinbox */
+ mail_flag (sysibx,tmp,"\\Deleted",ST_SET);
+ }
+
+ else { /* failed to snarf */
+ if (fd) { /* did it ever get opened? */
+ close (fd); /* close descriptor */
+ unlink (LOCAL->buf);/* flush this file */
+ }
+ sprintf (tmp,"Message copy to MH mailbox failed: %.80s",
+ s,strerror (errno));
+ mm_log (tmp,ERROR);
+ r = 0; /* stop the snarf in its tracks */
+ }
+ }
+ /* update scan time */
+ if (!stat (LOCAL->dir,&sbuf)) LOCAL->scantime = sbuf.st_ctime;
+ mail_expunge (sysibx); /* now expunge all those messages */
+ }
+ if (sysibx) mail_close (sysibx);
+ mm_nocritical (stream); /* release critical */
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of mailbox size */
+ mail_recent (stream,recent);
+ return T; /* return that we are alive */
+}
+
+/* MH mail check mailbox
+ * Accepts: MAIL stream
+ */
+
+void mh_check (MAILSTREAM *stream)
+{
+ /* Perhaps in the future this will preserve flags */
+ if (mh_ping (stream)) mm_log ("Check completed",(long) NIL);
+}
+
+
+/* MH mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long mh_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ MESSAGECACHE *elt;
+ unsigned long i = 1;
+ unsigned long n = 0;
+ unsigned long recent = stream->recent;
+ if (ret = sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) {
+ mm_critical (stream); /* go critical */
+ while (i <= stream->nmsgs) {/* for each message */
+ elt = mail_elt (stream,i);/* if deleted, need to trash it */
+ if (elt->deleted && (sequence ? elt->sequence : T)) {
+ sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
+ if (unlink (LOCAL->buf)) {/* try to delete the message */
+ sprintf (LOCAL->buf,"Expunge of message %lu failed, aborted: %s",i,
+ strerror (errno));
+ mm_log (LOCAL->buf,(long) NIL);
+ break;
+ }
+ /* note uncached */
+ LOCAL->cachedtexts -= ((elt->private.msg.header.text.data ?
+ elt->private.msg.header.text.size : 0) +
+ (elt->private.msg.text.text.data ?
+ elt->private.msg.text.text.size : 0));
+ mail_gc_msg (&elt->private.msg,GC_ENV | GC_TEXTS);
+ /* if recent, note one less recent message */
+ if (elt->recent) --recent;
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else i++; /* otherwise try next message */
+ }
+ if (n) { /* output the news if any expunged */
+ sprintf (LOCAL->buf,"Expunged %lu messages",n);
+ mm_log (LOCAL->buf,(long) NIL);
+ }
+ else mm_log ("No messages deleted, so no update needed",(long) NIL);
+ mm_nocritical (stream); /* release critical */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ }
+ return ret;
+}
+
+/* MH mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long mh_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ FDDATA d;
+ STRING st;
+ MESSAGECACHE *elt;
+ struct stat sbuf;
+ int fd;
+ unsigned long i;
+ char flags[MAILTMPLEN],date[MAILTMPLEN];
+ appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
+ long ret = NIL;
+ /* copy the messages */
+ if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
+ if ((fd = open (LOCAL->buf,O_RDONLY,NIL)) < 0) return NIL;
+ fstat (fd,&sbuf); /* get size of message */
+ if (!elt->day) { /* set internaldate to file date if needed */
+ struct tm *tm = gmtime (&sbuf.st_mtime);
+ elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
+ elt->year = tm->tm_year + 1900 - BASEYEAR;
+ elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
+ elt->seconds = tm->tm_sec;
+ elt->zhours = 0; elt->zminutes = 0;
+ }
+ d.fd = fd; /* set up file descriptor */
+ d.pos = 0; /* start of file */
+ d.chunk = LOCAL->buf;
+ d.chunksize = CHUNKSIZE;
+ /* kludge; mh_append would just strip CRs */
+ INIT (&st,fd_string,&d,sbuf.st_size);
+ /* init flag string */
+ flags[0] = flags[1] = '\0';
+ if (elt->seen) strcat (flags," \\Seen");
+ if (elt->deleted) strcat (flags," \\Deleted");
+ if (elt->flagged) strcat (flags," \\Flagged");
+ if (elt->answered) strcat (flags," \\Answered");
+ if (elt->draft) strcat (flags," \\Draft");
+ flags[0] = '('; /* open list */
+ strcat (flags,")"); /* close list */
+ mail_date (date,elt); /* generate internal date */
+ if (au) mail_parameters (NIL,SET_APPENDUID,NIL);
+ if ((ret = mail_append_full (NIL,mailbox,flags,date,&st)) &&
+ (options & CP_MOVE)) elt->deleted = T;
+ if (au) mail_parameters (NIL,SET_APPENDUID,(void *) au);
+ close (fd);
+ }
+ if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
+ mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN);
+ return ret; /* return success */
+}
+
+/* MH mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mh_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct direct **names = NIL;
+ int fd;
+ char c,*flags,*date,*s,tmp[MAILTMPLEN];
+ STRING *message;
+ MESSAGECACHE elt;
+ FILE *df;
+ long i,size,last,nfiles;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = &mhproto;
+ /* make sure valid mailbox */
+ if (!mh_isvalid (mailbox,tmp,NIL)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!((!compare_cstring (mailbox,MHINBOX) ||
+ !compare_cstring (mailbox,"INBOX")) &&
+ (mh_file (tmp,MHINBOX) &&
+ dummy_create_path (stream,strcat (tmp,"/"),
+ get_dir_protection (mailbox))))) {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* falls through */
+ case 0: /* merely empty file? */
+ break;
+ case EINVAL:
+ sprintf (tmp,"Invalid MH-format mailbox name: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MH-format mailbox: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* get first message */
+ if (!(*af) (stream,data,&flags,&date,&message)) return NIL;
+ if ((nfiles = scandir (tmp,&names,mh_select,mh_numsort)) > 0) {
+ /* largest number */
+ last = atoi (names[nfiles-1]->d_name);
+ for (i = 0; i < nfiles; ++i) /* free directory */
+ fs_give ((void **) &names[i]);
+ }
+ else last = 0; /* no messages here yet */
+ if (s = (void *) names) fs_give ((void **) &s);
+
+ mm_critical (stream); /* go critical */
+ do {
+ if (!SIZE (message)) { /* guard against zero-length */
+ mm_log ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ if (date) { /* want to preserve date? */
+ /* yes, parse date into an elt */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ mm_log (tmp,ERROR);
+ ret = NIL;
+ break;
+ }
+ }
+ mh_file (tmp,mailbox); /* build file name we will use */
+ sprintf (tmp + strlen (tmp),"/%ld",++last);
+ if (((fd = open (tmp,O_WRONLY|O_CREAT|O_EXCL,
+ (long)mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0)||
+ !(df = fdopen (fd,"ab"))) {
+ sprintf (tmp,"Can't open append message: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL;
+ break;
+ }
+ /* copy the data w/o CR's */
+ for (size = 0,i = SIZE (message); i && ret; --i)
+ if (((c = SNX (message)) != '\015') && (putc (c,df) == EOF)) ret = NIL;
+ /* close the file */
+ if (!ret || fclose (df)) {
+ unlink (tmp); /* delete message */
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ ret = NIL;
+ }
+ if (ret) { /* set the date for this message */
+ if (date) mh_setdate (tmp,&elt);
+ /* get next message */
+ if (!(*af) (stream,data,&flags,&date,&message)) ret = NIL;
+ }
+ } while (ret && message);
+ mm_nocritical (stream); /* release critical */
+ if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
+ mm_log ("Can not return meaningful APPENDUID with this mailbox format",
+ WARN);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* MH file name selection test
+ * Accepts: candidate directory entry
+ * Returns: T to use file name, NIL to skip it
+ */
+
+int mh_select (struct direct *name)
+{
+ char c;
+ char *s = name->d_name;
+ while (c = *s++) if (!isdigit (c)) return NIL;
+ return T;
+}
+
+
+/* MH file name comparision
+ * Accepts: first candidate directory entry
+ * second candidate directory entry
+ * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
+ */
+
+int mh_numsort (const void *d1,const void *d2)
+{
+ return atoi ((*(struct direct **) d1)->d_name) -
+ atoi ((*(struct direct **) d2)->d_name);
+}
+
+
+/* MH mail build file name
+ * Accepts: destination string
+ * source
+ * Returns: destination
+ */
+
+char *mh_file (char *dst,char *name)
+{
+ char *s;
+ char *path = mh_path (dst);
+ if (!path) fatal ("No mh path in mh_file()!");
+ /* INBOX becomes "inbox" in the MH path */
+ if (!compare_cstring (name,MHINBOX) || !compare_cstring (name,"INBOX"))
+ sprintf (dst,"%.900s/%.80s",path,MHINBOXDIR);
+ /* #mh names skip past prefix */
+ else if (*name == '#') sprintf (dst,"%.100s/%.900s",path,name + 4);
+ else mailboxfile (dst,name); /* all other names */
+ /* tie off unnecessary trailing / */
+ if ((s = strrchr (dst,'/')) && !s[1] && (s[-1] == '/')) *s = '\0';
+ return dst;
+}
+
+/* MH canonicalize name
+ * Accepts: buffer to write name
+ * reference
+ * pattern
+ * Returns: T if success, NIL if failure
+ */
+
+long mh_canonicalize (char *pattern,char *ref,char *pat)
+{
+ unsigned long i;
+ char *s,tmp[MAILTMPLEN];
+ if (ref && *ref) { /* have a reference */
+ strcpy (pattern,ref); /* copy reference to pattern */
+ /* # overrides mailbox field in reference */
+ if (*pat == '#') strcpy (pattern,pat);
+ /* pattern starts, reference ends, with / */
+ else if ((*pat == '/') && (pattern[strlen (pattern) - 1] == '/'))
+ strcat (pattern,pat + 1); /* append, omitting one of the period */
+ else strcat (pattern,pat); /* anything else is just appended */
+ }
+ else strcpy (pattern,pat); /* just have basic name */
+ if (mh_isvalid (pattern,tmp,T)) {
+ /* count wildcards */
+ for (i = 0, s = pattern; *s; *s++) if ((*s == '*') || (*s == '%')) ++i;
+ /* success if not too many */
+ if (i <= MAXWILDCARDS) return LONGT;
+ mm_log ("Excessive wildcards in LIST/LSUB",ERROR);
+ }
+ return NIL;
+}
+
+/* Set date for message
+ * Accepts: file name
+ * elt containing date
+ */
+
+void mh_setdate (char *file,MESSAGECACHE *elt)
+{
+ time_t tp[2];
+ tp[0] = time (0); /* atime is now */
+ tp[1] = mail_longdate (elt); /* modification time */
+ utime (file,tp); /* set the times */
+}
diff --git a/imap/src/osdep/unix/mix.c b/imap/src/osdep/unix/mix.c
new file mode 100644
index 00000000..fbf4a023
--- /dev/null
+++ b/imap/src/osdep/unix/mix.c
@@ -0,0 +1,2834 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MIX mail routines
+ *
+ * Author(s): Mark Crispin
+ * UW Technology
+ * University of Washington
+ * Seattle, WA 98195
+ * Internet: MRC@Washington.EDU
+ *
+ * Date: 1 March 2006
+ * Last Edited: 7 May 2008
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+/* MIX definitions */
+
+#define MEGABYTE (1024*1024)
+
+#define MIXDATAROLL MEGABYTE /* size at which we roll to a new file */
+
+
+/* MIX files */
+
+#define MIXNAME ".mix" /* prefix for all MIX file names */
+#define MIXMETA "meta" /* suffix for metadata */
+#define MIXINDEX "index" /* suffix for index */
+#define MIXSTATUS "status" /* suffix for status */
+#define MIXSORTCACHE "sortcache"/* suffix for sortcache */
+#define METAMAX (MEGABYTE-1) /* maximum metadata file size (sanity check) */
+
+
+/* MIX file formats */
+
+ /* sequence format (all but msg files) */
+#define SEQFMT "S%08lx\015\012"
+ /* metadata file format */
+#define MTAFMT "V%08lx\015\012L%08lx\015\012N%08lx\015\012"
+ /* index file record format */
+#define IXRFMT ":%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:%08lx:%08lx:%08lx:%08lx:\015\012"
+ /* status file record format */
+#define STRFMT ":%08lx:%08lx:%04x:%08lx:\015\012"
+ /* message file header format */
+#define MSRFMT "%s%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:\015\012"
+#define MSGTOK ":msg:"
+#define MSGTSZ (sizeof(MSGTOK)-1)
+ /* sortcache file record format */
+#define SCRFMT ":%08lx:%08lx:%08lx:%08lx:%08lx:%c%08lx:%08lx:%08lx:\015\012"
+
+/* MIX I/O stream local data */
+
+typedef struct mix_local {
+ unsigned long curmsg; /* current message file number */
+ unsigned long newmsg; /* current new message file number */
+ time_t lastsnarf; /* last snarf time */
+ int msgfd; /* file description of current msg file */
+ int mfd; /* file descriptor of open metadata */
+ unsigned long metaseq; /* metadata sequence */
+ char *index; /* mailbox index name */
+ unsigned long indexseq; /* index sequence */
+ char *status; /* mailbox status name */
+ unsigned long statusseq; /* status sequence */
+ char *sortcache; /* mailbox sortcache name */
+ unsigned long sortcacheseq; /* sortcache sequence */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned int expok : 1; /* non-zero if expunge reports OK */
+ unsigned int internal : 1; /* internally opened, do not validate */
+} MIXLOCAL;
+
+
+#define MIXBURP struct mix_burp
+
+MIXBURP {
+ unsigned long fileno; /* message file number */
+ char *name; /* message file name */
+ SEARCHSET *tail; /* tail of ranges */
+ SEARCHSET set; /* set of retained ranges */
+ MIXBURP *next; /* next file to burp */
+};
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MIXLOCAL *) stream->local)
+
+/* Function prototypes */
+
+DRIVER *mix_valid (char *name);
+long mix_isvalid (char *name,char *meta);
+void *mix_parameters (long function,void *value);
+long mix_dirfmttest (char *name);
+void mix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+long mix_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz);
+void mix_list (MAILSTREAM *stream,char *ref,char *pat);
+void mix_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mix_subscribe (MAILSTREAM *stream,char *mailbox);
+long mix_unsubscribe (MAILSTREAM *stream,char *mailbox);
+long mix_create (MAILSTREAM *stream,char *mailbox);
+long mix_delete (MAILSTREAM *stream,char *mailbox);
+long mix_rename (MAILSTREAM *stream,char *old,char *newname);
+int mix_rselect (struct direct *name);
+MAILSTREAM *mix_open (MAILSTREAM *stream);
+void mix_close (MAILSTREAM *stream,long options);
+void mix_abort (MAILSTREAM *stream);
+char *mix_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags);
+long mix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void mix_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+unsigned long *mix_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
+ SORTPGM *pgm,long flags);
+THREADNODE *mix_thread (MAILSTREAM *stream,char *type,char *charset,
+ SEARCHPGM *spg,long flags);
+long mix_ping (MAILSTREAM *stream);
+void mix_check (MAILSTREAM *stream);
+long mix_expunge (MAILSTREAM *stream,char *sequence,long options);
+int mix_select (struct direct *name);
+int mix_msgfsort (const void *d1,const void *d2);
+long mix_addset (SEARCHSET **set,unsigned long start,unsigned long size);
+long mix_burp (MAILSTREAM *stream,MIXBURP *burp,unsigned long *reclaimed);
+long mix_burp_check (SEARCHSET *set,size_t size,char *file);
+long mix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,
+ long options);
+long mix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+long mix_append_msg (MAILSTREAM *stream,FILE *f,char *flags,MESSAGECACHE *delt,
+ STRING *msg,SEARCHSET *set,unsigned long seq);
+
+FILE *mix_parse (MAILSTREAM *stream,FILE **idxf,long iflags,long sflags);
+char *mix_meta_slurp (MAILSTREAM *stream,unsigned long *seq);
+long mix_meta_update (MAILSTREAM *stream);
+long mix_index_update (MAILSTREAM *stream,FILE *idxf,long flag);
+long mix_status_update (MAILSTREAM *stream,FILE *statf,long flag);
+FILE *mix_data_open (MAILSTREAM *stream,int *fd,long *size,
+ unsigned long newsize);
+FILE *mix_sortcache_open (MAILSTREAM *stream);
+long mix_sortcache_update (MAILSTREAM *stream,FILE **sortcache);
+char *mix_read_record (FILE *f,char *buf,unsigned long buflen,char *type);
+unsigned long mix_read_sequence (FILE *f);
+char *mix_dir (char *dst,char *name);
+char *mix_file (char *dst,char *dir,char *name);
+char *mix_file_data (char *dst,char *dir,unsigned long data);
+unsigned long mix_modseq (unsigned long oldseq);
+
+/* MIX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mixdriver = {
+ "mix", /* driver name */
+ /* driver flags */
+ DR_MAIL|DR_LOCAL|DR_NOFAST|DR_CRLF|DR_LOCKING|DR_DIRFMT|DR_MODSEQ,
+ (DRIVER *) NIL, /* next driver */
+ mix_valid, /* mailbox is valid for us */
+ mix_parameters, /* manipulate parameters */
+ mix_scan, /* scan mailboxes */
+ mix_list, /* find mailboxes */
+ mix_lsub, /* find subscribed mailboxes */
+ mix_subscribe, /* subscribe to mailbox */
+ mix_unsubscribe, /* unsubscribe from mailbox */
+ mix_create, /* create mailbox */
+ mix_delete, /* delete mailbox */
+ mix_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ mix_open, /* open mailbox */
+ mix_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mix_header, /* fetch message header only */
+ mix_text, /* fetch message body only */
+ NIL, /* fetch partial message test */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ mix_flag, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ mix_sort, /* sort messages */
+ mix_thread, /* thread messages */
+ mix_ping, /* ping mailbox to see if still alive */
+ mix_check, /* check for new messages */
+ mix_expunge, /* expunge deleted messages */
+ mix_copy, /* copy messages to another mailbox */
+ mix_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mixproto = {&mixdriver};
+
+/* MIX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mix_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return mix_isvalid (name,tmp) ? &mixdriver : NIL;
+}
+
+
+/* MIX mail test for valid mailbox
+ * Accepts: mailbox name
+ * buffer to return meta name
+ * Returns: T if valid, NIL otherwise, metadata name written in both cases
+ */
+
+long mix_isvalid (char *name,char *meta)
+{
+ char dir[MAILTMPLEN];
+ struct stat sbuf;
+ /* validate name as directory */
+ if (!(errno = ((strlen (name) > NETMAXMBX) ? ENAMETOOLONG : NIL)) &&
+ *mix_dir (dir,name) && mix_file (meta,dir,MIXMETA) &&
+ !stat (dir,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
+ /* name is directory; is it mix? */
+ if (!stat (meta,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG))
+ return LONGT;
+ else errno = NIL; /* directory but not mix */
+ }
+ return NIL;
+}
+
+/* MIX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mix_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = mailboxfile ((char *) value,"~/INBOX");
+ break;
+ case GET_DIRFMTTEST:
+ ret = (void *) mix_dirfmttest;
+ break;
+ case GET_SCANCONTENTS:
+ ret = (void *) mix_scan_contents;
+ break;
+ case SET_ONETIMEEXPUNGEATPING:
+ if (value) ((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T;
+ case GET_ONETIMEEXPUNGEATPING:
+ if (value) ret = (void *)
+ (((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL);
+ break;
+ }
+ return ret;
+}
+
+
+/* MIX test for directory format internal node
+ * Accepts: candidate node name
+ * Returns: T if internal name, NIL otherwise
+ */
+
+long mix_dirfmttest (char *name)
+{
+ /* belongs to MIX if starts with .mix */
+ return strncmp (name,MIXNAME,sizeof (MIXNAME) - 1) ? NIL : LONGT;
+}
+
+/* MIX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MIX scan mailbox for contents
+ * Accepts: mailbox name
+ * desired contents
+ * contents size
+ * file size (ignored)
+ * Returns: NIL if contents not found, T if found
+ */
+
+long mix_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz)
+{
+ long i,nfiles;
+ void *a;
+ char *s;
+ long ret = NIL;
+ size_t namelen = strlen (name);
+ struct stat sbuf;
+ struct direct **names = NIL;
+ if ((nfiles = scandir (name,&names,mix_select,mix_msgfsort)) > 0)
+ for (i = 0; i < nfiles; ++i) {
+ if (!ret) {
+ sprintf (s = (char *) fs_get (namelen + strlen (names[i]->d_name) + 2),
+ "%s/%s",name,names[i]->d_name);
+ if (!stat (s,&sbuf) && (csiz <= sbuf.st_size))
+ ret = dummy_scan_contents (s,contents,csiz,sbuf.st_size);
+ fs_give ((void **) &s);
+ }
+ fs_give ((void **) &names[i]);
+ }
+ /* free directory list */
+ if (a = (void *) names) fs_give ((void **) &a);
+ return ret;
+}
+
+/* MIX list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mix_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MIX list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mix_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MIX mail subscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to add to subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long mix_subscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return sm_subscribe (mailbox);
+}
+
+
+/* MIX mail unsubscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to delete from subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long mix_unsubscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return sm_unsubscribe (mailbox);
+}
+
+/* MIX mail create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mix_create (MAILSTREAM *stream,char *mailbox)
+{
+ DRIVER *test;
+ FILE *f;
+ int c,i;
+ char *t,tmp[MAILTMPLEN],file[MAILTMPLEN];
+ char *s = strrchr (mailbox,'/');
+ unsigned long now = time (NIL);
+ long ret = NIL;
+ /* always create \NoSelect if trailing / */
+ if (s && !s[1]) return dummy_create (stream,mailbox);
+ /* validate name */
+ if (mix_dirfmttest (s ? s + 1 : mailbox))
+ sprintf(tmp,"Can't create mailbox %.80s: invalid MIX-format name",mailbox);
+ /* must not already exist */
+ else if ((test = mail_valid (NIL,mailbox,NIL)) &&
+ strcmp (test->name,"dummy"))
+ sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox);
+ /* create directory and metadata */
+ else if (!dummy_create_path (stream,
+ mix_file (file,mix_dir (tmp,mailbox),MIXMETA),
+ get_dir_protection (mailbox)))
+ sprintf (tmp,"Can't create mailbox %.80s: %.80s",mailbox,strerror (errno));
+ else if (!(f = fopen (file,"w")))
+ sprintf (tmp,"Can't re-open metadata %.80s: %.80s",mailbox,
+ strerror (errno));
+ else { /* success, write initial metadata */
+ fprintf (f,SEQFMT,now);
+ fprintf (f,MTAFMT,now,0,now);
+ for (i = 0, c = 'K'; (i < NUSERFLAGS) &&
+ (t = (stream && stream->user_flags[i]) ? stream->user_flags[i] :
+ default_user_flag (i)) && *t; ++i) {
+ putc (c,f); /* write another keyword */
+ fputs (t,f);
+ c = ' '; /* delimiter is now space */
+ }
+ fclose (f);
+ set_mbx_protections (mailbox,file);
+ /* point to suffix */
+ s = file + strlen (file) - (sizeof (MIXMETA) - 1);
+ strcpy (s,MIXINDEX); /* create index */
+ if (!dummy_create_path (stream,file,get_dir_protection (mailbox)))
+ sprintf (tmp,"Can't create mix mailbox index: %.80s",strerror (errno));
+ else {
+ set_mbx_protections (mailbox,file);
+ strcpy (s,MIXSTATUS); /* create status */
+ if (!dummy_create_path (stream,file,get_dir_protection (mailbox)))
+ sprintf (tmp,"Can't create mix mailbox status: %.80s",
+ strerror (errno));
+ else {
+ set_mbx_protections (mailbox,file);
+ sprintf (s,"%08lx",now);/* message file */
+ if (!dummy_create_path (stream,file,get_dir_protection (mailbox)))
+ sprintf (tmp,"Can't create mix mailbox data: %.80s",
+ strerror (errno));
+ else {
+ set_mbx_protections (mailbox,file);
+ ret = LONGT; /* declare success at this point */
+ }
+ }
+ }
+ }
+ if (!ret) MM_LOG (tmp,ERROR); /* some error */
+ return ret;
+}
+
+/* MIX mail delete mailbox
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mix_delete (MAILSTREAM *stream,char *mailbox)
+{
+ DIR *dirp;
+ struct direct *d;
+ int fd = -1;
+ char *s,tmp[MAILTMPLEN];
+ if (!mix_isvalid (mailbox,tmp))
+ sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox);
+ else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB))
+ sprintf (tmp,"Can't lock mailbox for delete: %.80s",mailbox);
+ /* delete metadata */
+ else if (unlink (tmp)) sprintf (tmp,"Can't delete mailbox %.80s index: %80s",
+ mailbox,strerror (errno));
+ else {
+ close (fd); /* close descriptor on deleted metadata */
+ /* get directory name */
+ *(s = strrchr (tmp,'/')) = '\0';
+ if (dirp = opendir (tmp)) { /* open directory */
+ *s++ = '/'; /* restore delimiter */
+ /* massacre messages */
+ while (d = readdir (dirp)) if (mix_dirfmttest (d->d_name)) {
+ strcpy (s,d->d_name); /* make path */
+ unlink (tmp); /* sayonara */
+ }
+ closedir (dirp); /* flush directory */
+ *(s = strrchr (tmp,'/')) = '\0';
+ if (rmdir (tmp)) { /* try to remove the directory */
+ sprintf (tmp,"Can't delete name %.80s: %.80s",
+ mailbox,strerror (errno));
+ MM_LOG (tmp,WARN);
+ }
+ }
+ return T; /* always success */
+ }
+ if (fd >= 0) close (fd); /* close any descriptor on metadata */
+ MM_LOG (tmp,ERROR); /* something failed */
+ return NIL;
+}
+
+/* MIX mail rename mailbox
+ * Accepts: MIX mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long mix_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN];
+ struct stat sbuf;
+ int fd = -1;
+ if (!mix_isvalid (old,tmp))
+ sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old);
+ else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB))
+ sprintf (tmp,"Can't lock mailbox for rename: %.80s",old);
+ else if (mix_dirfmttest ((s = strrchr (newname,'/')) ? s + 1 : newname))
+ sprintf (tmp,"Can't rename to mailbox %.80s: invalid MIX-format name",
+ newname);
+ /* new mailbox name must not be valid */
+ else if (mix_isvalid (newname,tmp))
+ sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists",
+ newname);
+ else {
+ mix_dir (tmp,old); /* build old directory name */
+ mix_dir (tmp1,newname); /* and new directory name */
+ /* easy if not INBOX */
+ if (compare_cstring (old,"INBOX")) {
+ /* found superior to destination name? */
+ if (s = strrchr (tmp1,'/')) {
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp1,get_dir_protection (newname)))
+ return NIL;
+ *s = c; /* restore full name */
+ }
+ if (!rename (tmp,tmp1)) {
+ close (fd); /* close descriptor on metadata */
+ return LONGT;
+ }
+ }
+
+ /* RFC 3501 requires this */
+ else if (dummy_create_path (stream,strcat (tmp1,"/"),
+ get_dir_protection (newname))) {
+ void *a;
+ int i,n,lasterror;
+ char *src,*dst;
+ struct direct **names = NIL;
+ size_t srcl = strlen (tmp);
+ size_t dstl = strlen (tmp1);
+ /* rename each mix file to new directory */
+ for (i = lasterror = 0,n = scandir (tmp,&names,mix_rselect,alphasort);
+ i < n; ++i) {
+ size_t len = strlen (names[i]->d_name);
+ sprintf (src = (char *) fs_get (srcl + len + 2),"%s/%s",
+ tmp,names[i]->d_name);
+ sprintf (dst = (char *) fs_get (dstl + len + 1),"%s%s",
+ tmp1,names[i]->d_name);
+ if (rename (src,dst)) lasterror = errno;
+ fs_give ((void **) &src);
+ fs_give ((void **) &dst);
+ fs_give ((void **) &names[i]);
+ }
+ /* free directory list */
+ if (a = (void *) names) fs_give ((void **) &a);
+ if (lasterror) errno = lasterror;
+ else {
+ close (fd); /* close descriptor on metadata */
+ return mix_create (NIL,"INBOX");
+ }
+ }
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",
+ old,newname,strerror (errno));
+ }
+ if (fd >= 0) close (fd); /* close any descriptor on metadata */
+ MM_LOG (tmp,ERROR); /* something failed */
+ return NIL;
+}
+
+
+/* MIX test for mix name
+ * Accepts: candidate directory name
+ * Returns: T if mix file name, NIL otherwise
+ */
+
+int mix_rselect (struct direct *name)
+{
+ return mix_dirfmttest (name->d_name);
+}
+
+/* MIX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mix_open (MAILSTREAM *stream)
+{
+ short silent;
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&mixproto);
+ if (stream->local) fatal ("mix recycle stream");
+ stream->local = memset (fs_get (sizeof (MIXLOCAL)),0,sizeof (MIXLOCAL));
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ /* make temporary buffer */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ /* set stream->mailbox to be directory name */
+ mix_dir (LOCAL->buf,stream->mailbox);
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (LOCAL->buf);
+ LOCAL->msgfd = -1; /* currently no file open */
+ if (!(((!stream->rdonly && /* open metadata file */
+ ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA),
+ O_RDWR,NIL)) >= 0)) ||
+ ((stream->rdonly = T) &&
+ ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA),
+ O_RDONLY,NIL)) >= 0))) &&
+ !flock (LOCAL->mfd,LOCK_SH))) {
+ MM_LOG ("Error opening mix metadata file",ERROR);
+ mix_abort (stream);
+ stream = NIL; /* open fails */
+ }
+ else { /* metadata open, complete open */
+ LOCAL->index = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXINDEX));
+ LOCAL->status = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXSTATUS));
+ LOCAL->sortcache = cpystr (mix_file (LOCAL->buf,stream->mailbox,
+ MIXSORTCACHE));
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (silent = stream->silent) LOCAL->internal = T;
+ stream->silent = T;
+ if (mix_ping (stream)) { /* do initial ping */
+ /* try burping in case we are exclusive */
+ if (!stream->rdonly) mix_expunge (stream,"",NIL);
+ if (!(stream->nmsgs || stream->silent))
+ MM_LOG ("Mailbox is empty",(long) NIL);
+ stream->silent = silent; /* now notify upper level */
+ mail_exists (stream,stream->nmsgs);
+ stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
+ stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ stream->kwd_create = /* can we create new user flags? */
+ (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ? NIL : T;
+ }
+ else { /* got murdelyzed in ping */
+ mix_abort (stream);
+ stream = NIL;
+ }
+ }
+ return stream; /* return stream to caller */
+}
+
+/* MIX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mix_close (MAILSTREAM *stream,long options)
+{
+ if (LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ /* burp-only or expunge */
+ mix_expunge (stream,(options & CL_EXPUNGE) ? NIL : "",NIL);
+ mix_abort (stream);
+ stream->silent = silent; /* reset silent state */
+ }
+}
+
+
+/* MIX mail abort stream
+ * Accepts: MAIL stream
+ */
+
+void mix_abort (MAILSTREAM *stream)
+{
+ if (LOCAL) { /* only if a file is open */
+ /* close current message file if open */
+ if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
+ /* close current metadata file if open */
+ if (LOCAL->mfd >= 0) close (LOCAL->mfd);
+ if (LOCAL->index) fs_give ((void **) &LOCAL->index);
+ if (LOCAL->status) fs_give ((void **) &LOCAL->status);
+ if (LOCAL->sortcache) fs_give ((void **) &LOCAL->sortcache);
+ /* free local scratch buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* MIX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mix_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ unsigned long i,j,k;
+ int fd;
+ char *s,tmp[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ if (length) *length = 0; /* default return */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get elt */
+ /* is message in current message file? */
+ if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) {
+ if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
+ if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox,
+ elt->private.spare.data),
+ O_RDONLY,NIL)) < 0) return "";
+ /* got file */
+ LOCAL->curmsg = elt->private.spare.data;
+ }
+ lseek (LOCAL->msgfd,elt->private.special.offset,L_SET);
+ /* size of special data and header */
+ j = elt->private.msg.header.offset + elt->private.msg.header.text.size;
+ if (j > LOCAL->buflen) { /* is buffer big enough? */
+ /* no, make one that is */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = j) + 1);
+ }
+ /* Maybe someday validate internaldate too */
+ /* slurp special data + header, validate */
+ if ((read (LOCAL->msgfd,LOCAL->buf,j) == j) &&
+ !strncmp (LOCAL->buf,MSGTOK,MSGTSZ) &&
+ (elt->private.uid == strtoul ((char *) LOCAL->buf + MSGTSZ,&s,16)) &&
+ (*s++ == ':') && (s = strchr (s,':')) &&
+ (k = strtoul (s+1,&s,16)) && (*s++ == ':') &&
+ (s < (char *) (LOCAL->buf + elt->private.msg.header.offset))) {
+ /* won, set offset and size of message */
+ i = elt->private.msg.header.offset;
+ *length = elt->private.msg.header.text.size;
+ if (k != elt->rfc822_size) {
+ sprintf (tmp,"Inconsistency in mix message size, uid=%lx (%lu != %lu)",
+ elt->private.uid,elt->rfc822_size,k);
+ MM_LOG (tmp,WARN);
+ }
+ }
+ else { /* document the problem */
+ LOCAL->buf[100] = '\0'; /* tie off buffer at no more than 100 octets */
+ /* or at newline, whichever is first */
+ if (s = strpbrk (LOCAL->buf,"\015\012")) *s = '\0';
+ sprintf (tmp,"Error reading mix message header, uid=%lx, s=%.0lx, h=%s",
+ elt->private.uid,elt->rfc822_size,LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ *length = i = j = 0; /* default to empty */
+ }
+ LOCAL->buf[j] = '\0'; /* tie off buffer at the end */
+ return (char *) LOCAL->buf + i;
+}
+
+/* MIX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ unsigned long i;
+ FDDATA d;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);
+ /* is message in current message file? */
+ if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) {
+ if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
+ if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox,
+ elt->private.spare.data),
+ O_RDONLY,NIL)) < 0) return NIL;
+ /* got file */
+ LOCAL->curmsg = elt->private.spare.data;
+ }
+ /* doing non-peek fetch? */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ FILE *idxf; /* yes, process metadata/index/status */
+ FILE *statf = mix_parse (stream,&idxf,NIL,LONGT);
+ elt->seen = T; /* mark as seen */
+ MM_FLAGS (stream,elt->msgno);
+ /* update status file if possible */
+ if (statf && !stream->rdonly) {
+ elt->private.mod = LOCAL->statusseq = mix_modseq (LOCAL->statusseq);
+ mix_status_update (stream,statf,NIL);
+ }
+ if (idxf) fclose (idxf); /* release index and status file */
+ if (statf) fclose (statf);
+ }
+ d.fd = LOCAL->msgfd; /* set up file descriptor */
+ /* offset of message text */
+ d.pos = elt->private.special.offset + elt->private.msg.header.offset +
+ elt->private.msg.header.text.size;
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* chunk size */
+ INIT (bs,fd_string,&d,elt->rfc822_size - elt->private.msg.header.text.size);
+ return T;
+}
+
+/* MIX mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ */
+
+void mix_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned long i,uf,ffkey;
+ long f;
+ short nf;
+ FILE *idxf;
+ FILE *statf = mix_parse (stream,&idxf,NIL,LONGT);
+ unsigned long seq = mix_modseq (LOCAL->statusseq);
+ /* find first free key */
+ for (ffkey = 0; (ffkey < NUSERFLAGS) && stream->user_flags[ffkey]; ++ffkey);
+ /* parse sequence and flags */
+ if (((flags & ST_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) &&
+ ((f = mail_parse_flags (stream,flag,&uf)) || uf)) {
+ /* alter flags */
+ for (i = 1,nf = (flags & ST_SET) ? T : NIL; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted;
+ old.flagged = elt->flagged; old.answered = elt->answered;
+ old.draft = elt->draft; old.user_flags = elt->user_flags;
+ if (f&fSEEN) elt->seen = nf;
+ if (f&fDELETED) elt->deleted = nf;
+ if (f&fFLAGGED) elt->flagged = nf;
+ if (f&fANSWERED) elt->answered = nf;
+ if (f&fDRAFT) elt->draft = nf;
+ /* user flags */
+ if (flags & ST_SET) elt->user_flags |= uf;
+ else elt->user_flags &= ~uf;
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) ||
+ (old.answered != elt->answered) || (old.draft != elt->draft) ||
+ (old.user_flags != elt->user_flags)) {
+ if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq;
+ MM_FLAGS (stream,elt->msgno);
+ }
+ }
+ /* update status file after change */
+ if (statf && (seq == LOCAL->statusseq))
+ mix_status_update (stream,statf,NIL);
+ /* update metadata if created a keyword */
+ if ((ffkey < NUSERFLAGS) && stream->user_flags[ffkey] &&
+ !mix_meta_update (stream))
+ MM_LOG ("Error updating mix metadata after keyword creation",ERROR);
+ }
+ if (statf) fclose (statf); /* release status file if still open */
+ if (idxf) fclose (idxf); /* release index file */
+}
+
+/* MIX mail sort messages
+ * Accepts: mail stream
+ * character set
+ * search program
+ * sort program
+ * option flags
+ * Returns: vector of sorted message sequences or NIL if error
+ */
+
+unsigned long *mix_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
+ SORTPGM *pgm,long flags)
+{
+ unsigned long *ret;
+ FILE *sortcache = mix_sortcache_open (stream);
+ ret = mail_sort_msgs (stream,charset,spg,pgm,flags);
+ mix_sortcache_update (stream,&sortcache);
+ return ret;
+}
+
+
+/* MIX mail thread messages
+ * Accepts: mail stream
+ * thread type
+ * character set
+ * search program
+ * option flags
+ * Returns: thread node tree or NIL if error
+ */
+
+THREADNODE *mix_thread (MAILSTREAM *stream,char *type,char *charset,
+ SEARCHPGM *spg,long flags)
+{
+ THREADNODE *ret;
+ FILE *sortcache = mix_sortcache_open (stream);
+ ret = mail_thread_msgs (stream,type,charset,spg,flags,mail_sort_msgs);
+ mix_sortcache_update (stream,&sortcache);
+ return ret;
+}
+
+/* MIX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+static int snarfing = 0; /* lock against recursive snarfing */
+
+long mix_ping (MAILSTREAM *stream)
+{
+ FILE *idxf,*statf;
+ struct stat sbuf;
+ STRING msg;
+ MESSAGECACHE *elt;
+ int mfd,ifd,sfd;
+ unsigned long i,msglen;
+ char *message,date[MAILTMPLEN],flags[MAILTMPLEN];
+ MAILSTREAM *sysibx = NIL;
+ long ret = NIL;
+ long snarfok = LONGT;
+ /* time to snarf? */
+ if (stream->inbox && !stream->rdonly && !snarfing &&
+ (time (0) >= (LOCAL->lastsnarf +
+ (time_t) mail_parameters (NIL,GET_SNARFINTERVAL,NIL)))) {
+ appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
+ copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
+ MM_CRITICAL (stream); /* go critical */
+ snarfing = T; /* don't recursively snarf */
+ /* disable APPENDUID/COPYUID callbacks */
+ mail_parameters (NIL,SET_APPENDUID,NIL);
+ mail_parameters (NIL,SET_COPYUID,NIL);
+ /* sizes match and anything in sysinbox? */
+ if (!stat (sysinbox (),&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG) &&
+ sbuf.st_size && (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
+ !sysibx->rdonly && sysibx->nmsgs) {
+ /* for each message in sysibx mailbox */
+ for (i = 1; snarfok && (i <= sysibx->nmsgs); ++i)
+ if (!(elt = mail_elt (sysibx,i))->deleted &&
+ (message = mail_fetch_message (sysibx,i,&msglen,FT_PEEK)) &&
+ msglen) {
+ mail_date (date,elt); /* make internal date string */
+ /* make flag string */
+ flags[0] = flags[1] = '\0';
+ if (elt->seen) strcat (flags," \\Seen");
+ if (elt->flagged) strcat (flags," \\Flagged");
+ if (elt->answered) strcat (flags," \\Answered");
+ if (elt->draft) strcat (flags," \\Draft");
+ flags[0] = '(';
+ strcat (flags,")");
+ INIT (&msg,mail_string,message,msglen);
+ if (snarfok = mail_append_full (stream,"INBOX",flags,date,&msg)) {
+ char sequence[15];
+ sprintf (sequence,"%lu",i);
+ mail_flag (sysibx,sequence,"\\Deleted",ST_SET);
+ }
+ }
+
+ /* now expunge all those messages */
+ if (snarfok) mail_expunge (sysibx);
+ else {
+ sprintf (LOCAL->buf,"Can't copy new mail at message: %lu",i - 1);
+ MM_LOG (LOCAL->buf,WARN);
+ }
+ }
+ if (sysibx) mail_close (sysibx);
+ /* reenable APPENDUID/COPYUID */
+ mail_parameters (NIL,SET_APPENDUID,(void *) au);
+ mail_parameters (NIL,SET_COPYUID,(void *) cu);
+ snarfing = NIL; /* no longer snarfing */
+ MM_NOCRITICAL (stream); /* release critical */
+ LOCAL->lastsnarf = time (0);/* note time of last snarf */
+ }
+ /* expunging OK if global flag set */
+ if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T;
+ /* process metadata/index/status */
+ if (statf = mix_parse (stream,&idxf,LONGT,
+ (LOCAL->internal ? NIL : LONGT))) {
+ fclose (statf); /* just close the status file */
+ ret = LONGT; /* declare success */
+ }
+ if (idxf) fclose (idxf); /* release index file */
+ LOCAL->expok = NIL; /* expunge no longer OK */
+ if (!ret) mix_abort (stream); /* murdelyze stream if ping fails */
+ return ret;
+}
+
+
+/* MIX mail checkpoint mailbox (burp only)
+ * Accepts: MAIL stream
+ */
+
+void mix_check (MAILSTREAM *stream)
+{
+ if (stream->rdonly) /* won't do on readonly files! */
+ MM_LOG ("Checkpoint ignored on readonly mailbox",NIL);
+ /* do burp-only expunge action */
+ if (mix_expunge (stream,"",NIL)) MM_LOG ("Check completed",(long) NIL);
+}
+
+/* MIX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL, empty string for burp only
+ * expunge options
+ * Returns: T on success, NIL if failure
+ */
+
+long mix_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ FILE *idxf = NIL;
+ FILE *statf = NIL;
+ MESSAGECACHE *elt;
+ int ifd,sfd;
+ long ret;
+ unsigned long i;
+ unsigned long nexp = 0;
+ unsigned long reclaimed = 0;
+ int burponly = (sequence && !*sequence);
+ LOCAL->expok = T; /* expunge during ping is OK */
+ if (!(ret = burponly || !sequence ||
+ ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) || stream->rdonly);
+ /* read index and open status exclusive */
+ else if (statf = mix_parse (stream,&idxf,LONGT,
+ LOCAL->internal ? NIL : LONGT)) {
+ /* expunge unless just burping */
+ if (!burponly) for (i = 1; i <= stream->nmsgs;) {
+ elt = mail_elt (stream,i);/* need to expunge this message? */
+ if (sequence ? elt->sequence : elt->deleted) {
+ ++nexp; /* yes, make it so */
+ mail_expunged (stream,i);
+ }
+ else ++i; /* otherwise advance to next message */
+ }
+
+ /* burp if can get exclusive access */
+ if (!flock (LOCAL->mfd,LOCK_EX|LOCK_NB)) {
+ void *a;
+ struct direct **names = NIL;
+ long nfiles = scandir (stream->mailbox,&names,mix_select,mix_msgfsort);
+ if (nfiles > 0) { /* if have message files */
+ MIXBURP *burp,*cur;
+ /* initialize burp list */
+ for (i = 0, burp = cur = NIL; i < nfiles; ++i) {
+ MIXBURP *nxt = (MIXBURP *) memset (fs_get (sizeof (MIXBURP)),0,
+ sizeof (MIXBURP));
+ /* another file found */
+ if (cur) cur = cur->next = nxt;
+ else cur = burp = nxt;
+ cur->name = names[i]->d_name;
+ cur->fileno = strtoul (cur->name + sizeof (MIXNAME) - 1,NIL,16);
+ cur->tail = &cur->set;
+ fs_give ((void **) &names[i]);
+ }
+ /* now load ranges */
+ for (i = 1, cur = burp; ret && (i <= stream->nmsgs); i++) {
+ /* is this message in current set? */
+ elt = mail_elt (stream,i);
+ if (cur && (elt->private.spare.data != cur->fileno)) {
+ /* restart if necessary */
+ if (elt->private.spare.data < cur->fileno) cur = burp;
+ /* hunt for appropriate mailbox */
+ while (cur && (elt->private.spare.data > cur->fileno))
+ cur = cur->next;
+ /* ought to have found it now... */
+ if (cur && (elt->private.spare.data != cur->fileno)) cur = NIL;
+ }
+ /* if found, add to set */
+ if (cur) ret = mix_addset (&cur->tail,elt->private.special.offset,
+ elt->private.msg.header.offset +
+ elt->rfc822_size);
+ else { /* uh-oh */
+ sprintf (LOCAL->buf,"Can't locate mix message file %.08lx",
+ elt->private.spare.data);
+ MM_LOG (LOCAL->buf,ERROR);
+ ret = NIL;
+ }
+ }
+ if (ret) /* if no errors, burp all files */
+ for (cur = burp; ret && cur; cur = cur->next) {
+ /* if non-empty, burp it */
+ if (cur->set.last) ret = mix_burp (stream,cur,&reclaimed);
+ /* empty, delete it unless new msg file */
+ else if (mix_file_data (LOCAL->buf,stream->mailbox,cur->fileno) &&
+ ((cur->fileno == LOCAL->newmsg) ?
+ truncate (LOCAL->buf,0) : unlink (LOCAL->buf))) {
+ sprintf (LOCAL->buf,
+ "Can't delete empty message file %.80s: %.80s",
+ cur->name,strerror (errno));
+ MM_LOG (LOCAL->buf,WARN);
+ }
+ }
+ }
+ else MM_LOG ("No mix message files found during expunge",WARN);
+ /* free directory list */
+ if (a = (void *) names) fs_give ((void **) &a);
+ }
+
+ /* either way, re-acquire shared lock */
+ if (flock (LOCAL->mfd,LOCK_SH|LOCK_NB))
+ fatal ("Unable to re-acquire metadata shared lock!");
+ /* Do this step even if ret is NIL (meaning some burp problem)! */
+ if (nexp || reclaimed) { /* rewrite index and status if changed */
+ LOCAL->indexseq = mix_modseq (LOCAL->indexseq);
+ if (mix_index_update (stream,idxf,NIL)) {
+ LOCAL->statusseq = mix_modseq (LOCAL->statusseq);
+ /* set failure if update fails */
+ ret = mix_status_update (stream,statf,NIL);
+ }
+ }
+ }
+ if (statf) fclose (statf); /* close status if still open */
+ if (idxf) fclose (idxf); /* close index if still open */
+ LOCAL->expok = NIL; /* cancel expok */
+ if (ret) { /* only if success */
+ char *s = NIL;
+ if (nexp) sprintf (s = LOCAL->buf,"Expunged %lu messages",nexp);
+ else if (reclaimed)
+ sprintf (s=LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed);
+ else if (!burponly)
+ s = stream->rdonly ? "Expunge ignored on readonly mailbox" :
+ "No messages deleted, so no update needed";
+ if (s) MM_LOG (s,(long) NIL);
+ }
+ return ret;
+}
+
+/* MIX test for message file name
+ * Accepts: candidate directory name
+ * Returns: T if message file name, NIL otherwise
+ *
+ * ".mix" with no suffix was used by experimental versions
+ */
+
+int mix_select (struct direct *name)
+{
+ char c,*s;
+ /* make sure name has prefix */
+ if (mix_dirfmttest (name->d_name)) {
+ for (c = *(s = name->d_name + sizeof (MIXNAME) - 1); c && isxdigit (c);
+ c = *s++);
+ if (!c) return T; /* all-hex or no suffix */
+ }
+ return NIL; /* not suffix or non-hex */
+}
+
+
+/* MIX msg file name comparision
+ * Accepts: first candidate directory entry
+ * second candidate directory entry
+ * Returns: -1 if d1 < d2, 0 if d1 == d2, 1 d1 > d2
+ */
+
+int mix_msgfsort (const void *d1,const void *d2)
+{
+ char *n1 = (*(struct direct **) d1)->d_name + sizeof (MIXNAME) - 1;
+ char *n2 = (*(struct direct **) d2)->d_name + sizeof (MIXNAME) - 1;
+ return compare_ulong (*n1 ? strtoul (n1,NIL,16) : 0,
+ *n2 ? strtoul (n2,NIL,16) : 0);
+}
+
+
+/* MIX add a range to a set
+ * Accepts: pointer to set to add
+ * start of set
+ * size of set
+ * Returns: T if success, set updated, NIL otherwise
+ */
+
+long mix_addset (SEARCHSET **set,unsigned long start,unsigned long size)
+{
+ SEARCHSET *s = *set;
+ if (start < s->last) { /* sanity check */
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Backwards-running mix index %lu < %lu",start,s->last);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* range initially empty? */
+ if (!s->last) s->first = start;
+ else if (start > s->last) /* no, start new range if can't append */
+ (*set = s = s->next = mail_newsearchset ())->first = start;
+ s->last = start + size; /* end of current range */
+ return LONGT;
+}
+
+/* MIX burp message file
+ * Accepts: MAIL stream
+ * current burp block for this message
+ * Returns: T if successful, NIL if failed
+ */
+
+static char *staterr = "Error in stat of mix message file %.80s: %.80s";
+static char *truncerr = "Error truncating mix message file %.80s: %.80s";
+
+long mix_burp (MAILSTREAM *stream,MIXBURP *burp,unsigned long *reclaimed)
+{
+ MESSAGECACHE *elt;
+ SEARCHSET *set;
+ struct stat sbuf;
+ off_t rpos,wpos;
+ size_t size,wsize,wpending,written;
+ int fd;
+ FILE *f;
+ void *s;
+ unsigned long i;
+ long ret = NIL;
+ /* build file name */
+ mix_file_data (LOCAL->buf,stream->mailbox,burp->fileno);
+ /* need to burp at start or multiple ranges? */
+ if (!burp->set.first && !burp->set.next) {
+ /* easy case, single range at start of file */
+ if (stat (LOCAL->buf,&sbuf)) {
+ sprintf (LOCAL->buf,staterr,burp->name,strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ }
+ /* is this range sane? */
+ else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) {
+ /* if matches range then no burp needed! */
+ if (burp->set.last == sbuf.st_size) ret = LONGT;
+ /* just need to remove cruft at end */
+ else if (ret = !truncate (LOCAL->buf,burp->set.last))
+ *reclaimed += sbuf.st_size - burp->set.last;
+ else {
+ sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ }
+ }
+ }
+ /* have to do more work, get the file */
+ else if (((fd = open (LOCAL->buf,O_RDWR,NIL)) < 0) ||
+ !(f = fdopen (fd,"r+b"))) {
+ sprintf (LOCAL->buf,"Error opening mix message file %.80s: %.80s",
+ burp->name,strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ if (fd >= 0) close (fd); /* in case fdopen() failure */
+ }
+ else if (fstat (fd,&sbuf)) { /* get file size */
+ sprintf (LOCAL->buf,staterr,burp->name,strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ fclose (f);
+ }
+
+ /* only if sane */
+ else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) {
+ /* make sure each range starts with token */
+ for (set = &burp->set; set; set = set->next)
+ if (fseek (f,set->first,SEEK_SET) ||
+ (fread (LOCAL->buf,1,MSGTSZ,f) != MSGTSZ) ||
+ strncmp (LOCAL->buf,MSGTOK,MSGTSZ)) {
+ sprintf (LOCAL->buf,"Bad message token in mix message file at %lu",
+ set->first);
+ MM_LOG (LOCAL->buf,ERROR);
+ fclose (f);
+ return NIL; /* burp fails for this file */
+ }
+ /* burp out each old message */
+ for (set = &burp->set, wpos = 0; set; set = set->next) {
+ /* move down this range */
+ for (rpos = set->first, size = set->last - set->first;
+ size; size -= wsize) {
+ if (rpos != wpos) { /* data to skip at start? */
+ /* no, slide this buffer down */
+ wsize = min (size,LOCAL->buflen);
+ /* failure is not an option here */
+ while (fseek (f,rpos,SEEK_SET) ||
+ (fread (LOCAL->buf,1,wsize,f) != wsize)) {
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ /* nor here */
+ while (fseek (f,wpos,SEEK_SET)) {
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ /* and especially not here */
+ for (s = LOCAL->buf, wpending = wsize; wpending; wpending -= written)
+ if (!(written = fwrite (LOCAL->buf,1,wpending,f))) {
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ }
+ else wsize = size; /* nothing to skip, say we wrote it all */
+ rpos += wsize; wpos += wsize;
+ }
+ }
+
+ while (fflush (f)) { /* failure also not an option here... */
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ if (ftruncate (fd,wpos)) { /* flush cruft at end of file */
+ sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno));
+ MM_LOG (LOCAL->buf,WARN);
+ }
+ else *reclaimed += rpos - wpos;
+ ret = !fclose (f); /* close file */
+ /* slide down message positions in index */
+ for (i = 1,rpos = 0; i <= stream->nmsgs; ++i)
+ if ((elt = mail_elt (stream,i))->private.spare.data == burp->fileno) {
+ elt->private.special.offset = rpos;
+ rpos += elt->private.msg.header.offset + elt->rfc822_size;
+ }
+ /* debugging */
+ if (rpos != wpos) fatal ("burp size consistency check!");
+ }
+ return ret;
+}
+
+
+/* MIX burp sanity check to make sure not burping off end of file
+ * Accepts: burp set
+ * file size
+ * file name
+ * Returns: T if sane, NIL if insane
+ */
+
+long mix_burp_check (SEARCHSET *set,size_t size,char *file)
+{
+ do if (set->last > size) { /* sanity check */
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Unexpected short mix message file %.80s %lu < %lu",
+ file,size,set->last);
+ MM_LOG (tmp,ERROR);
+ return NIL; /* don't burp this file at all */
+ } while (set = set->next);
+ return LONGT;
+}
+
+/* MIX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long mix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ FDDATA d;
+ STRING st;
+ char tmp[2*MAILTMPLEN];
+ long ret = mix_isvalid (mailbox,LOCAL->buf);
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ MAILSTREAM *astream = NIL;
+ FILE *idxf = NIL;
+ FILE *msgf = NIL;
+ FILE *statf = NIL;
+ if (!ret) switch (errno) { /* make sure valid mailbox */
+ case NIL: /* no error in stat() */
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ break;
+ default: /* some stat() error */
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ break;
+ }
+ /* get sequence to copy */
+ else if (!(ret = ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))));
+ /* acquire stream to append */
+ else if (ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) &&
+ !astream->rdonly &&
+ (((MIXLOCAL *) astream->local)->expok = T) &&
+ (statf = mix_parse (astream,&idxf,LONGT,NIL))) ?
+ LONGT : NIL) {
+ int fd;
+ unsigned long i;
+ MESSAGECACHE *elt;
+ unsigned long newsize,hdrsize,size;
+ MIXLOCAL *local = (MIXLOCAL *) astream->local;
+ unsigned long seq = mix_modseq (local->metaseq);
+ /* make sure new modseq fits */
+ if (local->indexseq > seq) seq = local->indexseq + 1;
+ if (local->statusseq > seq) seq = local->statusseq + 1;
+ /* calculate size of per-message header */
+ sprintf (local->buf,MSRFMT,MSGTOK,0,0,0,0,0,0,0,'+',0,0,0);
+ hdrsize = strlen (local->buf);
+
+ MM_CRITICAL (stream); /* go critical */
+ astream->silent = T; /* no events here */
+ /* calculate size that will be added */
+ for (i = 1, newsize = 0; i <= stream->nmsgs; ++i)
+ if ((elt = mail_elt (stream,i))->sequence)
+ newsize += hdrsize + elt->rfc822_size;
+ /* open data file */
+ if (msgf = mix_data_open (astream,&fd,&size,newsize)) {
+ char *t;
+ unsigned long j,uid,uidv;
+ copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ for (i = 1,uid = uidv = 0; ret && (i <= stream->nmsgs); ++i)
+ if (((elt = mail_elt (stream,i))->sequence) && elt->rfc822_size) {
+ /* is message in current message file? */
+ if ((LOCAL->msgfd < 0) ||
+ (elt->private.spare.data != LOCAL->curmsg)) {
+ if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
+ if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,
+ stream->mailbox,
+ elt->private.spare.data),
+ O_RDONLY,NIL)) >= 0)
+ LOCAL->curmsg = elt->private.spare.data;
+ }
+ if (LOCAL->msgfd < 0) ret = NIL;
+ else { /* got file */
+ d.fd = LOCAL->msgfd;/* set up file descriptor */
+ /* start of message */
+ d.pos = elt->private.special.offset +
+ elt->private.msg.header.offset;
+ d.chunk = LOCAL->buf;
+ d.chunksize = CHUNKSIZE;
+ INIT (&st,fd_string,&d,elt->rfc822_size);
+ /* init flag string */
+ tmp[0] = tmp[1] = '\0';
+ if (j = elt->user_flags) do
+ if ((t = stream->user_flags[find_rightmost_bit (&j)]) && *t)
+ strcat (strcat (tmp," "),t);
+ while (j);
+ if (elt->seen) strcat (tmp," \\Seen");
+ if (elt->deleted) strcat (tmp," \\Deleted");
+ if (elt->flagged) strcat (tmp," \\Flagged");
+ if (elt->answered) strcat (tmp," \\Answered");
+ if (elt->draft) strcat (tmp," \\Draft");
+ tmp[0] = '('; /* wrap list */
+ strcat (tmp,")");
+ /* if append OK, add to source set */
+ if ((ret = mix_append_msg (astream,msgf,tmp,elt,&st,dest,
+ seq)) && source)
+ mail_append_set (source,mail_uid (stream,i));
+ }
+ }
+
+ /* finish write if success */
+ if (ret && (ret = !fflush (msgf))) {
+ fclose (msgf); /* all good, close the msg file now */
+ /* write new metadata, index, and status */
+ local->metaseq = local->indexseq = local->statusseq = seq;
+ if (ret = (mix_meta_update (astream) &&
+ mix_index_update (astream,idxf,LONGT))) {
+ /* success, delete if doing a move */
+ if (options & CP_MOVE)
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ elt->deleted = T;
+ if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq;
+ MM_FLAGS (stream,elt->msgno);
+ }
+ /* done with status file now */
+ mix_status_update (astream,statf,LONGT);
+ /* return sets if doing COPYUID */
+ if (cu) (*cu) (stream,mailbox,astream->uid_validity,source,dest);
+ source = dest = NIL; /* don't free these sets now */
+ }
+ }
+ else { /* error */
+ if (errno) { /* output error message if system call error */
+ sprintf (tmp,"Message copy failed: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ ftruncate (fd,size); /* revert file */
+ close (fd); /* make sure that fclose doesn't corrupt us */
+ fclose (msgf); /* free the stdio resources */
+ }
+ /* flush any sets remaining */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ else { /* message file open failed */
+ sprintf (tmp,"Error opening copy message file: %.80s",
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL;
+ }
+ MM_NOCRITICAL (stream);
+ }
+ else MM_LOG ("Can't open copy mailbox",ERROR);
+ if (statf) fclose (statf); /* close status if still open */
+ if (idxf) fclose (idxf); /* close index if still open */
+ /* finished with append stream */
+ if (astream) mail_close (astream);
+ return ret; /* return state */
+}
+
+/* MIX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ STRING *message;
+ char *flags,*date,tmp[MAILTMPLEN];
+ /* N.B.: can't use LOCAL->buf for tmp */
+ long ret = mix_isvalid (mailbox,tmp);
+ /* default stream to prototype */
+ if (!stream) stream = user_flags (&mixproto);
+ if (!ret) switch (errno) { /* if not valid mailbox */
+ case ENOENT: /* no such file? */
+ if (ret = compare_cstring (mailbox,"INBOX") ?
+ NIL : mix_create (NIL,"INBOX"))
+ break;
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ break;
+ default:
+ sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ break;
+ }
+
+ /* get first message */
+ if (ret && MM_APPEND (af) (stream,data,&flags,&date,&message)) {
+ MAILSTREAM *astream;
+ FILE *idxf = NIL;
+ FILE *msgf = NIL;
+ FILE *statf = NIL;
+ if (ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) &&
+ !astream->rdonly &&
+ (((MIXLOCAL *) astream->local)->expok = T) &&
+ (statf = mix_parse (astream,&idxf,LONGT,NIL))) ?
+ LONGT : NIL) {
+ int fd;
+ unsigned long size,hdrsize;
+ MESSAGECACHE elt;
+ MIXLOCAL *local = (MIXLOCAL *) astream->local;
+ unsigned long seq = mix_modseq (local->metaseq);
+ /* make sure new modseq fits */
+ if (local->indexseq > seq) seq = local->indexseq + 1;
+ if (local->statusseq > seq) seq = local->statusseq + 1;
+ /* calculate size of per-message header */
+ sprintf (local->buf,MSRFMT,MSGTOK,0,0,0,0,0,0,0,'+',0,0,0);
+ hdrsize = strlen (local->buf);
+ MM_CRITICAL (astream); /* go critical */
+ astream->silent = T; /* no events here */
+ /* open data file */
+ if (msgf = mix_data_open (astream,&fd,&size,hdrsize + SIZE (message))) {
+ appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ while (ret && message) {/* while good to go and have messages */
+ errno = NIL; /* in case one of these causes failure */
+ /* guard against zero-length */
+ if (!(ret = SIZE (message)))
+ MM_LOG ("Append of zero-length message",ERROR);
+ else if (date && !(ret = mail_parse_date (&elt,date))) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ }
+ else {
+ if (!date) { /* if date not specified, use now */
+ internal_date (tmp);
+ mail_parse_date (&elt,tmp);
+ }
+ ret = mix_append_msg (astream,msgf,flags,&elt,message,dst,seq) &&
+ MM_APPEND (af) (stream,data,&flags,&date,&message);
+ }
+ }
+
+ /* finish write if success */
+ if (ret && (ret = !fflush (msgf))) {
+ fclose (msgf); /* all good, close the msg file now */
+ /* write new metadata, index, and status */
+ local->metaseq = local->indexseq = local->statusseq = seq;
+ if ((ret = (mix_meta_update (astream) &&
+ mix_index_update (astream,idxf,LONGT) &&
+ mix_status_update (astream,statf,LONGT))) && au) {
+ (*au) (mailbox,astream->uid_validity,dst);
+ dst = NIL; /* don't free this set now */
+ }
+ }
+ else { /* failure */
+ if (errno) { /* output error message if system call error */
+ sprintf (tmp,"Message append failed: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ ftruncate (fd,size); /* revert all writes to file*/
+ close (fd); /* make sure that fclose doesn't corrupt us */
+ fclose (msgf); /* free the stdio resources */
+ }
+ /* flush any set remaining */
+ mail_free_searchset (&dst);
+ }
+ else { /* message file open failed */
+ sprintf (tmp,"Error opening append message file: %.80s",
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL;
+ }
+ MM_NOCRITICAL (astream); /* release critical */
+ }
+ else MM_LOG ("Can't open append mailbox",ERROR);
+ if (statf) fclose (statf); /* close status if still open */
+ if (idxf) fclose (idxf); /* close index if still open */
+ if (astream) mail_close (astream);
+ }
+ return ret;
+}
+
+/* MIX mail append single message
+ * Accepts: MAIL stream
+ * flags for new message if non-NIL
+ * elt with source date if non-NIL
+ * stringstruct of message text
+ * searchset to place UID
+ * modseq of message
+ * Returns: T if success, NIL if failure
+ */
+
+long mix_append_msg (MAILSTREAM *stream,FILE *f,char *flags,MESSAGECACHE *delt,
+ STRING *msg,SEARCHSET *set,unsigned long seq)
+{
+ MESSAGECACHE *elt;
+ int c,cs;
+ unsigned long i,j,k,uf,hoff;
+ long sf;
+ stream->kwd_create = NIL; /* don't copy unknown keywords */
+ sf = mail_parse_flags (stream,flags,&uf);
+ /* swell the cache */
+ mail_exists (stream,++stream->nmsgs);
+ /* assign new UID from metadata */
+ (elt = mail_elt (stream,stream->nmsgs))->private.uid = ++stream->uid_last;
+ elt->private.mod = seq; /* set requested modseq in status */
+ elt->rfc822_size = SIZE (msg);/* copy message size and date to index */
+ elt->year = delt->year; elt->month = delt->month; elt->day = delt->day;
+ elt->hours = delt->hours; elt->minutes = delt->minutes;
+ elt->seconds = delt->seconds; elt->zoccident = delt->zoccident;
+ elt->zhours = delt->zhours; elt->zminutes = delt->zminutes;
+ /*
+ * Do NOT set elt->valid here! mix_status_update() uses it to determine
+ * whether a message should be marked as old.
+ */
+ if (sf&fSEEN) elt->seen = T; /* copy flags to status */
+ if (sf&fDELETED) elt->deleted = T;
+ if (sf&fFLAGGED) elt->flagged = T;
+ if (sf&fANSWERED) elt->answered = T;
+ if (sf&fDRAFT) elt->draft = T;
+ elt->user_flags |= uf;
+ /* message is in new message file */
+ elt->private.spare.data = LOCAL->newmsg;
+
+ /* offset to message internal header */
+ elt->private.special.offset = ftell (f);
+ /* build header for message */
+ fprintf (f,MSRFMT,MSGTOK,elt->private.uid,
+ elt->year + BASEYEAR,elt->month,elt->day,
+ elt->hours,elt->minutes,elt->seconds,
+ elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes,
+ elt->rfc822_size);
+ /* offset to header from internal header */
+ elt->private.msg.header.offset = ftell (f) - elt->private.special.offset;
+ for (cs = 0; SIZE (msg); ) { /* copy message */
+ if (elt->private.msg.header.text.size) {
+ if (msg->cursize) /* blat entire chunk if have it */
+ for (j = msg->cursize; j; j -= k)
+ if (!(k = fwrite (msg->curpos,1,j,f))) return NIL;
+ SETPOS (msg,GETPOS (msg) + msg->cursize);
+ }
+ else { /* still searching for delimiter */
+ c = 0xff & SNX (msg); /* get source character */
+ if (putc (c,f) == EOF) return NIL;
+ switch (cs) { /* decide what to do based on state */
+ case 0: /* previous char ordinary */
+ if (c == '\015') cs = 1;/* advance if CR */
+ break;
+ case 1: /* previous CR, advance if LF */
+ cs = (c == '\012') ? 2 : 0;
+ break;
+ case 2: /* previous CRLF, advance if CR */
+ cs = (c == '\015') ? 3 : 0;
+ break;
+ case 3: /* previous CRLFCR, done if LF */
+ if (c == '\012') elt->private.msg.header.text.size =
+ elt->rfc822_size - SIZE (msg);
+ cs = 0; /* reset mechanism */
+ break;
+ }
+ }
+ }
+ /* if no delimiter, header is entire msg */
+ if (!elt->private.msg.header.text.size)
+ elt->private.msg.header.text.size = elt->rfc822_size;
+ /* add this message to set */
+ mail_append_set (set,elt->private.uid);
+ return LONGT; /* success */
+}
+
+/* MIX mail read metadata, index, and status
+ * Accepts: MAIL stream
+ * returned index file
+ * index file flags (non-NIL if want to add/remove messages)
+ * status file flags (non-NIL if want to update elt->valid and old)
+ * Returns: open status file, or NIL if failure
+ *
+ * Note that this routine can return an open index file even if it fails!
+ */
+
+static char *shortmsg =
+ "message %lu (UID=%.08lx) truncated by %lu byte(s) (%lu < %lu)";
+
+FILE *mix_parse (MAILSTREAM *stream,FILE **idxf,long iflags,long sflags)
+{
+ int fd;
+ unsigned long i;
+ char *s,*t;
+ struct stat sbuf;
+ FILE *statf = NIL;
+ short metarepairneeded = 0;
+ short indexrepairneeded = 0;
+ short silent = stream->silent;
+ *idxf = NIL; /* in case error */
+ /* readonly means no updates */
+ if (stream->rdonly) iflags = sflags = NIL;
+ /* open index file */
+ if ((fd = open (LOCAL->index,iflags ? O_RDWR : O_RDONLY,NIL)) < 0)
+ MM_LOG ("Error opening mix index file",ERROR);
+ /* acquire exclusive access and FILE */
+ else if (!flock (fd,iflags ? LOCK_EX : LOCK_SH) &&
+ !(*idxf = fdopen (fd,iflags ? "r+b" : "rb"))) {
+ MM_LOG ("Error obtaining stream on mix index file",ERROR);
+ flock (fd,LOCK_UN); /* relinquish lock */
+ close (fd);
+ }
+
+ /* slurp metadata */
+ else if (s = mix_meta_slurp (stream,&i)) {
+ unsigned long j = 0; /* non-zero if UIDVALIDITY/UIDLAST changed */
+ if (i != LOCAL->metaseq) { /* metadata changed? */
+ char *t,*k;
+ LOCAL->metaseq = i; /* note new metadata sequence */
+ while (s && *s) { /* parse entire metadata file */
+ /* locate end of line */
+ if (s = strstr (t = s,"\015\012")) {
+ *s = '\0'; /* tie off line */
+ s += 2; /* skip past CRLF */
+ switch (*t++) { /* parse line */
+ case 'V': /* UIDVALIDITY */
+ if (!isxdigit (*t) || !(i = strtoul (t,&t,16))) {
+ MM_LOG ("Error in mix metadata file UIDVALIDITY record",ERROR);
+ return NIL; /* give up */
+ }
+ if (i != stream->uid_validity) j = stream->uid_validity = i;
+ break;
+ case 'L': /* new UIDLAST */
+ if (!isxdigit (*t)) {
+ MM_LOG ("Error in mix metadata file UIDLAST record",ERROR);
+ return NIL; /* give up */
+ }
+ if ((i = strtoul (t,&t,16)) != stream->uid_last)
+ j = stream->uid_last = i;
+ break;
+ case 'N': /* new message file */
+ if (!isxdigit (*t)) {
+ MM_LOG ("Error in mix metadata file new msg record",ERROR);
+ return NIL; /* give up */
+ }
+ if ((i = strtoul (t,&t,16)) != stream->uid_last)
+ LOCAL->newmsg = i;
+ break;
+ case 'K': /* new keyword list */
+ for (i = 0; t && *t && (i < NUSERFLAGS); ++i) {
+ if (t = strchr (k = t,' ')) *t++ = '\0';
+ /* make sure keyword non-empty */
+ if (*k && (strlen (k) <= MAXUSERFLAG)) {
+ /* in case value changes (shouldn't happen) */
+ if (stream->user_flags[i] && strcmp (stream->user_flags[i],k)){
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"flag rename old=%.80s new=%.80s",
+ stream->user_flags[i],k);
+ MM_LOG (tmp,WARN);
+ fs_give ((void **) &stream->user_flags[i]);
+ }
+ if (!stream->user_flags[i]) stream->user_flags[i] = cpystr (k);
+ }
+ else break; /* empty keyword */
+ }
+ if ((i < NUSERFLAGS) && stream->user_flags[i]) {
+ MM_LOG ("Error in mix metadata file keyword record",ERROR);
+ return NIL; /* give up */
+ }
+ else if (i == NUSERFLAGS) stream->kwd_create = NIL;
+ break;
+ }
+ }
+ if (t && *t) { /* junk in line */
+ MM_LOG ("Error in mix metadata record",ERROR);
+ return NIL; /* give up */
+ }
+ }
+ }
+
+ /* get sequence */
+ if (!(i = mix_read_sequence (*idxf)) || (i < LOCAL->indexseq)) {
+ MM_LOG ("Error in mix index file sequence record",ERROR);
+ return NIL; /* give up */
+ }
+ /* sequence changed from last time? */
+ else if (j || (i > LOCAL->indexseq)) {
+ unsigned long uid,nmsgs,curfile,curfilesize,curpos;
+ char *t,*msg,tmp[MAILTMPLEN];
+ /* start with no messages */
+ curfile = curfilesize = curpos = nmsgs = 0;
+ /* update sequence iff expunging OK */
+ if (LOCAL->expok) LOCAL->indexseq = i;
+ /* get first elt */
+ while ((s = mix_read_record (*idxf,LOCAL->buf,LOCAL->buflen,"index")) &&
+ *s)
+ switch (*s) {
+ case ':': /* message record */
+ if (!(isxdigit (*++s) && (uid = strtoul (s,&t,16)))) msg = "UID";
+ else if (!((*t++ == ':') && isdigit (*t) && isdigit (t[1]) &&
+ isdigit (t[2]) && isdigit (t[3]) && isdigit (t[4]) &&
+ isdigit (t[5]) && isdigit (t[6]) && isdigit (t[7]) &&
+ isdigit (t[8]) && isdigit (t[9]) && isdigit (t[10]) &&
+ isdigit (t[11]) && isdigit (t[12]) && isdigit (t[13]) &&
+ ((t[14] == '+') || (t[14] == '-')) &&
+ isdigit (t[15]) && isdigit (t[16]) && isdigit (t[17]) &&
+ isdigit (t[18]))) msg = "internaldate";
+ else if ((*(s = t+19) != ':') || !isxdigit (*++s)) msg = "size";
+ else {
+ unsigned int y = (((*t - '0') * 1000) + ((t[1] - '0') * 100) +
+ ((t[2] - '0') * 10) + t[3] - '0') - BASEYEAR;
+ unsigned int m = ((t[4] - '0') * 10) + t[5] - '0';
+ unsigned int d = ((t[6] - '0') * 10) + t[7] - '0';
+ unsigned int hh = ((t[8] - '0') * 10) + t[9] - '0';
+ unsigned int mm = ((t[10] - '0') * 10) + t[11] - '0';
+ unsigned int ss = ((t[12] - '0') * 10) + t[13] - '0';
+ unsigned int z = (t[14] == '-') ? 1 : 0;
+ unsigned int zh = ((t[15] - '0') * 10) + t[16] - '0';
+ unsigned int zm = ((t[17] - '0') * 10) + t[18] - '0';
+ unsigned long size = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ unsigned long file = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ unsigned long pos = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ unsigned long hpos = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ unsigned long hsiz = strtoul (s,&s,16);
+ if (uid > stream->uid_last) {
+ sprintf (tmp,"mix index invalid UID (%08lx < %08lx)",
+ uid,stream->uid_last);
+ if (stream->rdonly) {
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ strcat (tmp,", repaired");
+ MM_LOG (tmp,WARN);
+ stream->uid_last = uid;
+ metarepairneeded = T;
+ }
+
+ /* ignore expansion values */
+ if (*s++ == ':') {
+ MESSAGECACHE *elt;
+ ++nmsgs; /* this is another mesage */
+ /* within current known range of messages? */
+ while (nmsgs <= stream->nmsgs) {
+ /* yes, get corresponding elt */
+ elt = mail_elt (stream,nmsgs);
+ /* existing message with matching data? */
+ if (uid == elt->private.uid) {
+ /* beware of Dracula's resurrection */
+ if (elt->private.ghost) {
+ sprintf (tmp,"mix index data unexpunged UID: %lx",
+ uid);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* also of static data changing */
+ if ((size != elt->rfc822_size) ||
+ (file != elt->private.spare.data) ||
+ (pos != elt->private.special.offset) ||
+ (hpos != elt->private.msg.header.offset) ||
+ (hsiz != elt->private.msg.header.text.size) ||
+ (y != elt->year) || (m != elt->month) ||
+ (d != elt->day) || (hh != elt->hours) ||
+ (mm != elt->minutes) || (ss != elt->seconds) ||
+ (z != elt->zoccident) || (zh != elt->zhours) ||
+ (zm != elt->zminutes)) {
+ sprintf (tmp,"mix index data mismatch: %lx",uid);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ break;
+ }
+ /* existing msg with lower UID is expunged */
+ else if (uid > elt->private.uid) {
+ if (LOCAL->expok) mail_expunged (stream,nmsgs);
+ else {/* message expunged, but not yet for us */
+ ++nmsgs;
+ elt->private.ghost = T;
+ }
+ }
+ else { /* unexpected message record */
+ sprintf (tmp,"mix index UID mismatch (%lx < %lx)",
+ uid,elt->private.uid);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ }
+
+ /* time to create a new message? */
+ if (nmsgs > stream->nmsgs) {
+ /* defer announcing until later */
+ stream->silent = T;
+ mail_exists (stream,nmsgs);
+ stream->silent = silent;
+ (elt = mail_elt (stream,nmsgs))->recent = T;
+ elt->private.uid = uid; elt->rfc822_size = size;
+ elt->private.spare.data = file;
+ elt->private.special.offset = pos;
+ elt->private.msg.header.offset = hpos;
+ elt->private.msg.header.text.size = hsiz;
+ elt->year = y; elt->month = m; elt->day = d;
+ elt->hours = hh; elt->minutes = mm;
+ elt->seconds = ss; elt->zoccident = z;
+ elt->zhours = zh; elt->zminutes = zm;
+ /* message in same file? */
+ if (curfile == file) {
+ if (pos < curpos) {
+ MESSAGECACHE *plt = mail_elt (stream,elt->msgno-1);
+ /* uh-oh, calculate delta? */
+ i = curpos - pos;
+ sprintf (tmp,shortmsg,plt->msgno,plt->private.uid,
+ i,pos,curpos);
+ /* possible to fix? */
+ if (!stream->rdonly && LOCAL->expok &&
+ (i < plt->rfc822_size)) {
+ plt->rfc822_size -= i;
+ if (plt->rfc822_size <
+ plt->private.msg.header.text.size)
+ plt->private.msg.header.text.size =
+ plt->rfc822_size;
+ strcat (tmp,", repaired");
+ indexrepairneeded = T;
+ }
+ MM_LOG (tmp,WARN);
+ }
+ }
+ else { /* new file, restart */
+ if (stat (mix_file_data (LOCAL->buf,stream->mailbox,
+ curfile = file),&sbuf)) {
+ sprintf (tmp,"Missing mix data file: %.500s",
+ LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ curfile = file;
+ curfilesize = sbuf.st_size;
+ }
+
+ /* position of message in file */
+ curpos = pos + elt->private.msg.header.offset +
+ elt->rfc822_size;
+ /* short file? */
+ if (curfilesize < curpos) {
+ /* uh-oh, calculate delta? */
+ i = curpos - curfilesize;
+ sprintf (tmp,shortmsg,elt->msgno,elt->private.uid,
+ i,curfilesize,curpos);
+ /* possible to fix? */
+ if (!stream->rdonly && LOCAL->expok &&
+ (i < elt->rfc822_size)) {
+ elt->rfc822_size -= i;
+ if (elt->rfc822_size <
+ elt->private.msg.header.text.size)
+ elt->private.msg.header.text.size =
+ elt->rfc822_size;
+ strcat (tmp,", repaired");
+ indexrepairneeded = T;
+ }
+ MM_LOG (tmp,WARN);
+ }
+ }
+ break;
+ }
+ else msg = "expansion";
+ }
+ else msg = "header size";
+ }
+ else msg = "header position";
+ }
+ else msg = "message position";
+ }
+ else msg = "file#";
+ }
+ sprintf (tmp,"Error in %s in mix index file: %.500s",msg,s);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Unknown record in mix index file: %.500s",s);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ if (!s) return NIL; /* barfage from mix_read_record() */
+ /* expunge trailing messages not in index */
+ if (LOCAL->expok) while (nmsgs < stream->nmsgs)
+ mail_expunged (stream,stream->nmsgs);
+ }
+
+ /* repair metadata and index if needed */
+ if ((metarepairneeded ? mix_meta_update (stream) : T) &&
+ (indexrepairneeded ? mix_index_update (stream,*idxf,NIL) : T)) {
+ MESSAGECACHE *elt;
+ int fd;
+ unsigned long uid,uf,sf,mod;
+ char *s;
+ int updatep = NIL;
+ /* open status file */
+ if ((fd = open (LOCAL->status,
+ stream->rdonly ? O_RDONLY : O_RDWR,NIL)) < 0)
+ MM_LOG ("Error opening mix status file",ERROR);
+ /* acquire exclusive access and FILE */
+ else if (!flock (fd,stream->rdonly ? LOCK_SH : LOCK_EX) &&
+ !(statf = fdopen (fd,stream->rdonly ? "rb" : "r+b"))) {
+ MM_LOG ("Error obtaining stream on mix status file",ERROR);
+ flock (fd,LOCK_UN); /* relinquish lock */
+ close (fd);
+ }
+ /* get sequence */
+ else if (!(i = mix_read_sequence (statf)) ||
+ ((i < LOCAL->statusseq) && stream->nmsgs && (i != 1))) {
+ sprintf (LOCAL->buf,
+ "Error in mix status sequence record, i=%lx, seq=%lx",
+ i,LOCAL->statusseq);
+ MM_LOG (LOCAL->buf,ERROR);
+ }
+ /* sequence changed from last time? */
+ else if (i != LOCAL->statusseq) {
+ /* update sequence, get first elt */
+ if (i > LOCAL->statusseq) LOCAL->statusseq = i;
+ if (stream->nmsgs) {
+ elt = mail_elt (stream,i = 1);
+
+ /* read message records */
+ while ((t = s = mix_read_record (statf,LOCAL->buf,LOCAL->buflen,
+ "status")) && *s && (*s++ == ':') &&
+ isxdigit (*s)) {
+ uid = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ uf = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ sf = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ mod = strtoul (s,&s,16);
+ /* ignore expansion values */
+ if (*s++ == ':') {
+ /* need to move ahead to next elt? */
+ while ((uid > elt->private.uid) && (i < stream->nmsgs))
+ elt = mail_elt (stream,++i);
+ /* update elt if altered */
+ if ((uid == elt->private.uid) &&
+ (!elt->valid || (mod != elt->private.mod))) {
+ elt->user_flags = uf;
+ elt->private.mod = mod;
+ elt->seen = (sf & fSEEN) ? T : NIL;
+ elt->deleted = (sf & fDELETED) ? T : NIL;
+ elt->flagged = (sf & fFLAGGED) ? T : NIL;
+ elt->answered = (sf & fANSWERED) ? T : NIL;
+ elt->draft = (sf & fDRAFT) ? T : NIL;
+ /* announce if altered existing message */
+ if (elt->valid) MM_FLAGS (stream,elt->msgno);
+ /* first time, is old message? */
+ else if (sf & fOLD) {
+ /* yes, clear recent and set valid */
+ elt->recent = NIL;
+ elt->valid = T;
+ }
+ /* recent, allowed to update its status? */
+ else if (sflags) {
+ /* yes, set valid and check in status */
+ elt->valid = T;
+ elt->private.mod = mix_modseq (elt->private.mod);
+ updatep = T;
+ }
+ /* leave valid unset and recent if sflags not set */
+ }
+ continue; /* everything looks good */
+ }
+ }
+ }
+ }
+ break; /* error somewhere */
+ }
+
+ if (t && *t) { /* non-null means bogus record */
+ char msg[MAILTMPLEN];
+ sprintf (msg,"Error in mix status file message record%s: %.80s",
+ stream->rdonly ? "" : ", fixing",t);
+ MM_LOG (msg,WARN);
+ /* update it if not readonly */
+ if (!stream->rdonly) updatep = T;
+ }
+ if (updatep) { /* need to update? */
+ LOCAL->statusseq = mix_modseq (LOCAL->statusseq);
+ mix_status_update (stream,statf,LONGT);
+ }
+ }
+ }
+ }
+ }
+ if (statf) { /* still happy? */
+ unsigned long j;
+ stream->silent = silent; /* now notify upper level */
+ mail_exists (stream,stream->nmsgs);
+ for (i = 1, j = 0; i <= stream->nmsgs; ++i)
+ if (mail_elt (stream,i)->recent) ++j;
+ mail_recent (stream,j);
+ }
+ return statf;
+}
+
+/* MIX metadata file routines */
+
+/* MIX read metadata
+ * Accepts: MAIL stream
+ * return pointer for modseq
+ * Returns: pointer to metadata after modseq or NIL if failure
+ */
+
+char *mix_meta_slurp (MAILSTREAM *stream,unsigned long *seq)
+{
+ struct stat sbuf;
+ char *s;
+ char *ret = NIL;
+ if (fstat (LOCAL->mfd,&sbuf))
+ MM_LOG ("Error obtaining size of mix metatdata file",ERROR);
+ if (sbuf.st_size > LOCAL->buflen) {
+ /* should be just a few dozen bytes */
+ if (sbuf.st_size > METAMAX) fatal ("absurd mix metadata file size");
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = sbuf.st_size) + 1);
+ }
+ /* read current metadata file */
+ LOCAL->buf[sbuf.st_size] = '\0';
+ if (lseek (LOCAL->mfd,0,L_SET) ||
+ (read (LOCAL->mfd,s = LOCAL->buf,sbuf.st_size) != sbuf.st_size))
+ MM_LOG ("Error reading mix metadata file",ERROR);
+ else if ((*s != 'S') || !isxdigit (s[1]) ||
+ ((*seq = strtoul (s+1,&s,16)) < LOCAL->metaseq) ||
+ (*s++ != '\015') || (*s++ != '\012'))
+ MM_LOG ("Error in mix metadata file sequence record",ERROR);
+ else ret = s;
+ return ret;
+}
+
+/* MIX update metadata
+ * Accepts: MAIL stream
+ * Returns: T on success, NIL if error
+ *
+ * Index MUST be locked!!
+ */
+
+long mix_meta_update (MAILSTREAM *stream)
+{
+ long ret;
+ /* do nothing if stream readonly */
+ if (stream->rdonly) ret = LONGT;
+ else {
+ unsigned char c,*s,*ss,*t;
+ unsigned long i;
+ /* The worst-case metadata is limited to:
+ * 4 * (1 + 8 + 2) + (NUSERFLAGS * (MAXUSERFLAG + 1))
+ * which comes out to 1994 octets. This is much smaller than the normal
+ * CHUNKSIZE definition of 64K, and CHUNKSIZE is the smallest size of
+ * LOCAL->buf.
+ *
+ * If more stuff gets added to the metadata, or if you change the value
+ * of NUSERFLAGS, MAXUSERFLAG or CHUNKSIZE, be sure to recalculate the
+ * above assertation.
+ */
+ sprintf (LOCAL->buf,SEQFMT,LOCAL->metaseq = mix_modseq (LOCAL->metaseq));
+ sprintf (LOCAL->buf + strlen (LOCAL->buf),MTAFMT,
+ stream->uid_validity,stream->uid_last,LOCAL->newmsg);
+ for (i = 0, c = 'K', s = ss = LOCAL->buf + strlen (LOCAL->buf);
+ (i < NUSERFLAGS) && (t = stream->user_flags[i]); ++i) {
+ if (!*t) fatal ("impossible empty keyword");
+ *s++ = c; /* write delimiter */
+ while (*t) *s++ = *t++; /* write keyword */
+ c = ' '; /* delimiter is now space */
+ }
+ if (s != ss) { /* tie off keywords line */
+ *s++ = '\015'; *s++ = '\012';
+ }
+ /* calculate length of metadata */
+ if ((i = s - LOCAL->buf) > LOCAL->buflen)
+ fatal ("impossible buffer overflow");
+ lseek (LOCAL->mfd,0,L_SET); /* rewind file */
+ /* write new metadata */
+ ret = (write (LOCAL->mfd,LOCAL->buf,i) == i) ? LONGT : NIL;
+ ftruncate (LOCAL->mfd,i); /* and tie off at that point */
+ }
+ return ret;
+}
+
+/* MIX index file routines */
+
+
+/* MIX update index
+ * Accepts: MAIL stream
+ * open FILE
+ * expansion check flag
+ * Returns: T on success, NIL if error
+ */
+
+long mix_index_update (MAILSTREAM *stream,FILE *idxf,long flag)
+{
+ unsigned long i;
+ long ret = LONGT;
+ if (!stream->rdonly) { /* do nothing if stream readonly */
+ if (flag) { /* need to do expansion check? */
+ char tmp[MAILTMPLEN];
+ size_t size;
+ struct stat sbuf;
+ /* calculate file size we need */
+ for (i = 1, size = 0; i <= stream->nmsgs; ++i)
+ if (!mail_elt (stream,i)->private.ghost) ++size;
+ if (size) { /* Winston Smith's first dairy entry */
+ sprintf (tmp,IXRFMT,0,14,4,4,13,0,0,'+',0,0,0,0,0,0,0);
+ size *= strlen (tmp);
+ }
+ /* calculate file size we need */
+ sprintf (tmp,SEQFMT,LOCAL->indexseq);
+ size += strlen (tmp);
+ /* get current file size */
+ if (fstat (fileno (idxf),&sbuf)) {
+ MM_LOG ("Error getting size of mix index file",ERROR);
+ ret = NIL;
+ }
+ /* need to write additional space? */
+ else if (sbuf.st_size < size) {
+ void *buf = fs_get (size -= sbuf.st_size);
+ memset (buf,0,size);
+ if (fseek (idxf,0,SEEK_END) || (fwrite (buf,1,size,idxf) != size) ||
+ fflush (idxf)) {
+ fseek (idxf,sbuf.st_size,SEEK_SET);
+ ftruncate (fileno (idxf),sbuf.st_size);
+ MM_LOG ("Error extending mix index file",ERROR);
+ ret = NIL;
+ }
+ fs_give ((void **) &buf);
+ }
+ }
+
+ if (ret) { /* if still good to go */
+ rewind (idxf); /* let's start at the very beginning */
+ /* write modseq first */
+ fprintf (idxf,SEQFMT,LOCAL->indexseq);
+ /* then write all messages */
+ for (i = 1; ret && (i <= stream->nmsgs); i++) {
+ MESSAGECACHE *elt = mail_elt (stream,i);
+ if (!elt->private.ghost)/* only write living messages */
+ fprintf (idxf,IXRFMT,elt->private.uid,
+ elt->year + BASEYEAR,elt->month,elt->day,
+ elt->hours,elt->minutes,elt->seconds,
+ elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes,
+ elt->rfc822_size,elt->private.spare.data,
+ elt->private.special.offset,
+ elt->private.msg.header.offset,
+ elt->private.msg.header.text.size);
+ if (ferror (idxf)) {
+ MM_LOG ("Error updating mix index file",ERROR);
+ ret = NIL;
+ }
+ }
+ if (fflush (idxf)) {
+ MM_LOG ("Error flushing mix index file",ERROR);
+ ret = NIL;
+ }
+ if (ret) ftruncate (fileno (idxf),ftell (idxf));
+ }
+ }
+ return ret;
+}
+
+/* MIX status file routines */
+
+
+/* MIX update status
+ * Accepts: MAIL stream
+ * pointer to open FILE
+ * expansion check flag
+ * Returns: T on success, NIL if error
+ */
+
+long mix_status_update (MAILSTREAM *stream,FILE *statf,long flag)
+{
+ unsigned long i;
+ char tmp[MAILTMPLEN];
+ long ret = LONGT;
+ if (!stream->rdonly) { /* do nothing if stream readonly */
+ if (flag) { /* need to do expansion check? */
+ char tmp[MAILTMPLEN];
+ size_t size;
+ struct stat sbuf;
+ /* calculate file size we need */
+ for (i = 1, size = 0; i <= stream->nmsgs; ++i)
+ if (!mail_elt (stream,i)->private.ghost) ++size;
+ if (size) { /* number of living messages */
+ sprintf (tmp,STRFMT,0,0,0,0);
+ size *= strlen (tmp);
+ }
+ sprintf (tmp,SEQFMT,LOCAL->statusseq);
+ size += strlen (tmp);
+ /* get current file size */
+ if (fstat (fileno (statf),&sbuf)) {
+ MM_LOG ("Error getting size of mix status file",ERROR);
+ ret = NIL;
+ }
+ /* need to write additional space? */
+ else if (sbuf.st_size < size) {
+ void *buf = fs_get (size -= sbuf.st_size);
+ memset (buf,0,size);
+ if (fseek (statf,0,SEEK_END) || (fwrite (buf,1,size,statf) != size) ||
+ fflush (statf)) {
+ fseek (statf,sbuf.st_size,SEEK_SET);
+ ftruncate (fileno (statf),sbuf.st_size);
+ MM_LOG ("Error extending mix status file",ERROR);
+ ret = NIL;
+ }
+ fs_give ((void **) &buf);
+ }
+ }
+
+ if (ret) { /* if still good to go */
+ rewind (statf); /* let's start at the very beginning */
+ /* write sequence */
+ fprintf (statf,SEQFMT,LOCAL->statusseq);
+ /* write message status records */
+ for (i = 1; ret && (i <= stream->nmsgs); ++i) {
+ MESSAGECACHE *elt = mail_elt (stream,i);
+ /* make sure all messages have a modseq */
+ if (!elt->private.mod) elt->private.mod = LOCAL->statusseq;
+ if (!elt->private.ghost)/* only write living messages */
+ fprintf (statf,STRFMT,elt->private.uid,elt->user_flags,
+ (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft) + (elt->valid ? fOLD : NIL),
+ elt->private.mod);
+ if (ferror (statf)) {
+ sprintf (tmp,"Error updating mix status file: %.80s",
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL;
+ }
+ }
+ if (ret && fflush (statf)) {
+ MM_LOG ("Error flushing mix status file",ERROR);
+ ret = NIL;
+ }
+ if (ret) ftruncate (fileno (statf),ftell (statf));
+ }
+ }
+ return ret;
+}
+
+/* MIX data file routines */
+
+
+/* MIX open data file
+ * Accepts: MAIL stream
+ * pointer to returned fd if success
+ * pointer to returned size if success
+ * size of new data to be added
+ * Returns: open FILE, or NIL if failure
+ *
+ * The curend test assumes that the last message of the mailbox is the furthest
+ * point that the current data file extends, and thus that is all that needs to
+ * be tested for short file prevention.
+ */
+
+FILE *mix_data_open (MAILSTREAM *stream,int *fd,long *size,
+ unsigned long newsize)
+{
+ FILE *msgf = NIL;
+ struct stat sbuf;
+ MESSAGECACHE *elt = stream->nmsgs ? mail_elt (stream,stream->nmsgs) : NIL;
+ unsigned long curend = (elt && (elt->private.spare.data == LOCAL->newmsg)) ?
+ elt->private.special.offset + elt->private.msg.header.offset +
+ elt->rfc822_size : 0;
+ /* allow create if curend 0 */
+ if ((*fd = open (mix_file_data (LOCAL->buf,stream->mailbox,LOCAL->newmsg),
+ O_RDWR | (curend ? NIL : O_CREAT),NIL)) >= 0) {
+ fstat (*fd,&sbuf); /* get current file size */
+ /* can we use this file? */
+ if ((curend <= sbuf.st_size) &&
+ (!sbuf.st_size || ((sbuf.st_size + newsize) <= MIXDATAROLL)))
+ *size = sbuf.st_size; /* yes, return current size */
+ else { /* short file or becoming too long */
+ if (curend > sbuf.st_size) {
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"short mix message file %.08lx (%ld > %ld), rolling",
+ LOCAL->newmsg,curend,sbuf.st_size);
+ MM_LOG (tmp,WARN); /* shouldn't happen */
+ }
+ close (*fd); /* roll to a new file */
+ while ((*fd = open (mix_file_data
+ (LOCAL->buf,stream->mailbox,
+ LOCAL->newmsg = mix_modseq (LOCAL->newmsg)),
+ O_RDWR | O_CREAT | O_EXCL,sbuf.st_mode)) < 0);
+ *size = 0; /* brand new file */
+ fchmod (*fd,sbuf.st_mode);/* with same mode as previous file */
+ }
+ }
+ if (*fd >= 0) { /* have a data file? */
+ /* yes, get stdio and set position */
+ if (msgf = fdopen (*fd,"r+b")) fseek (msgf,*size,SEEK_SET);
+ else close (*fd); /* fdopen() failed? */
+ }
+ return msgf; /* return results */
+}
+
+/* MIX open sortcache
+ * Accepts: MAIL stream
+ * Returns: open FILE, or NIL if failure or could only get readonly sortcache
+ */
+
+FILE *mix_sortcache_open (MAILSTREAM *stream)
+{
+ int fd,refwd;
+ unsigned long i,uid,sentdate,fromlen,tolen,cclen,subjlen,msgidlen,reflen;
+ char *s,*t,*msg,tmp[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ SORTCACHE *sc;
+ STRINGLIST *sl;
+ struct stat sbuf;
+ int rdonly = NIL;
+ FILE *srtcf = NIL;
+ mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
+ fstat (LOCAL->mfd,&sbuf);
+ if (!stream->nmsgs); /* do nothing if mailbox empty */
+ /* open sortcache file */
+ else if (((fd = open (LOCAL->sortcache,O_RDWR|O_CREAT,sbuf.st_mode)) < 0) &&
+ !(rdonly = ((fd = open (LOCAL->sortcache,O_RDONLY,NIL)) >= 0)))
+ MM_LOG ("Error opening mix sortcache file",WARN);
+ /* acquire lock and FILE */
+ else if (!flock (fd,rdonly ? LOCK_SH : LOCK_EX) &&
+ !(srtcf = fdopen (fd,rdonly ? "rb" : "r+b"))) {
+ MM_LOG ("Error obtaining stream on mix sortcache file",WARN);
+ flock (fd,LOCK_UN); /* relinquish lock */
+ close (fd);
+ }
+ else if (!(i = mix_read_sequence (srtcf)) || (i < LOCAL->sortcacheseq))
+ MM_LOG ("Error in mix sortcache file sequence record",WARN);
+ /* sequence changed from last time? */
+ else if (i > LOCAL->sortcacheseq) {
+ LOCAL->sortcacheseq = i; /* update sequence */
+ while ((s = t = mix_read_record (srtcf,LOCAL->buf,LOCAL->buflen,
+ "sortcache")) && *s &&
+ (msg = "uid") && (*s++ == ':') && isxdigit (*s)) {
+ uid = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ sentdate = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ fromlen = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ tolen = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ cclen = strtoul (s,&s,16);
+ if ((*s++ == ':') && ((*s == 'R') || (*s == ' ')) &&
+ isxdigit (s[1])) {
+ refwd = (*s++ == 'R') ? T : NIL;
+ subjlen = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ msgidlen = strtoul (s,&s,16);
+ if ((*s++ == ':') && isxdigit (*s)) {
+ reflen = strtoul (s,&s,16);
+ /* ignore expansion values */
+ if (*s++ == ':') {
+
+ if (i = mail_msgno (stream,uid)) {
+ sc = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE);
+ sc->size = (elt = mail_elt (stream,i))->rfc822_size;
+ sc->date = sentdate;
+ sc->arrival = elt->day ? mail_longdate (elt) : 1;
+ if (refwd) sc->refwd = T;
+ if (fromlen) {
+ if (sc->from) fseek (srtcf,fromlen + 2,SEEK_CUR);
+ else if ((getc (srtcf) != 'F') ||
+ (fread (sc->from = (char *) fs_get(fromlen),
+ 1,fromlen-1,srtcf) != (fromlen-1))||
+ (sc->from[fromlen-1] = '\0') ||
+ (getc (srtcf) != '\015') ||
+ (getc (srtcf) != '\012')) {
+ msg = "from data";
+ break;
+ }
+ }
+ if (tolen) {
+ if (sc->to) fseek (srtcf,tolen + 2,SEEK_CUR);
+ else if ((getc (srtcf) != 'T') ||
+ (fread (sc->to = (char *) fs_get (tolen),
+ 1,tolen-1,srtcf) != (tolen - 1)) ||
+ (sc->to[tolen-1] = '\0') ||
+ (getc (srtcf) != '\015') ||
+ (getc (srtcf) != '\012')) {
+ msg = "to data";
+ break;
+ }
+ }
+ if (cclen) {
+ if (sc->cc) fseek (srtcf,cclen + 2,SEEK_CUR);
+ else if ((getc (srtcf) != 'C') ||
+ (fread (sc->cc = (char *) fs_get (cclen),
+ 1,cclen-1,srtcf) != (cclen - 1)) ||
+ (sc->cc[cclen-1] = '\0') ||
+ (getc (srtcf) != '\015') ||
+ (getc (srtcf) != '\012')) {
+ msg = "cc data";
+ break;
+ }
+ }
+ if (subjlen) {
+ if (sc->subject) fseek (srtcf,subjlen + 2,SEEK_CUR);
+ else if ((getc (srtcf) != 'S') ||
+ (fread (sc->subject =
+ (char *) fs_get (subjlen),1,
+ subjlen-1,srtcf) != (subjlen-1))||
+ (sc->subject[subjlen-1] = '\0') ||
+ (getc (srtcf) != '\015') ||
+ (getc (srtcf) != '\012')) {
+ msg = "subject data";
+ break;
+ }
+ }
+
+ if (msgidlen) {
+ if (sc->message_id)
+ fseek (srtcf,msgidlen + 2,SEEK_CUR);
+ else if ((getc (srtcf) != 'M') ||
+ (fread (sc->message_id =
+ (char *) fs_get (msgidlen),1,
+ msgidlen-1,srtcf) != (msgidlen-1))||
+ (sc->message_id[msgidlen-1] = '\0') ||
+ (getc (srtcf) != '\015') ||
+ (getc (srtcf) != '\012')) {
+ msg = "message-id data";
+ break;
+ }
+ }
+ if (reflen) {
+ if (sc->references) fseek(srtcf,reflen + 2,SEEK_CUR);
+ /* make sure it fits */
+ else {
+ if (reflen >= LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *)
+ fs_get ((LOCAL->buflen = reflen) + 1);
+ }
+ if ((getc (srtcf) != 'R') ||
+ (fread (LOCAL->buf,1,reflen-1,srtcf) !=
+ (reflen - 1)) ||
+ (LOCAL->buf[reflen-1] = '\0') ||
+ (getc (srtcf) != '\015') ||
+ (getc (srtcf) != '\012')) {
+ msg = "references data";
+ break;
+ }
+ for (s = LOCAL->buf,sl = NIL,
+ sc->references = mail_newstringlist ();
+ s && *s; s += i + 1) {
+ if ((i = strtoul (s,&s,16)) && (*s++ == ':') &&
+ (s[i] == ':')) {
+ if (sl) sl = sl->next = mail_newstringlist();
+ else sl = sc->references;
+ s[i] = '\0';
+ sl->text.data = cpystr (s);
+ sl->text.size = i;
+ }
+ else s = NIL;
+ }
+ if (!s || *s ||
+ (s != ((char *) LOCAL->buf + reflen - 1))) {
+ msg = "references length consistency check";
+ break;
+ }
+ }
+ }
+ }
+
+ /* UID not found, ignore this message */
+ else fseek (srtcf,((fromlen ? fromlen + 2 : 0) +
+ (tolen ? tolen + 2 : 0) +
+ (cclen ? cclen + 2 : 0) +
+ (subjlen ? subjlen + 2 : 0) +
+ (msgidlen ? msgidlen + 2 : 0) +
+ (reflen ? reflen + 2 : 0)),
+ SEEK_CUR);
+ continue;
+ }
+ else msg = "expansion";
+ }
+ else msg = "references";
+ }
+ else msg = "message-id";
+ }
+ else msg = "subject";
+ }
+ else msg = "cc";
+ }
+ else msg = "to";
+ }
+ else msg = "from";
+ }
+ else msg = "sentdate";
+ break; /* error somewhere */
+ }
+ if (!t || *t) { /* error detected? */
+ if (t) { /* non-null means bogus record */
+ sprintf (tmp,"Error in %s in mix sortcache record: %.500s",msg,t);
+ MM_LOG (tmp,WARN);
+ }
+ fclose (srtcf); /* either way, must punt */
+ srtcf = NIL;
+ }
+ }
+ if (rdonly && srtcf) { /* can't update if readonly */
+ unlink (LOCAL->sortcache); /* try deleting it */
+ fclose (srtcf); /* so close it and return as if error */
+ srtcf = NIL;
+ }
+ else fchmod (fd,sbuf.st_mode);
+ return srtcf;
+}
+
+/* MIX update and close sortcache
+ * Accepts: MAIL stream
+ * pointer to open FILE (if FILE is NIL, do nothing)
+ * Returns: T on success, NIL on error
+ */
+
+long mix_sortcache_update (MAILSTREAM *stream,FILE **sortcache)
+{
+ FILE *f = *sortcache;
+ long ret = LONGT;
+ if (f) { /* ignore if no file */
+ unsigned long i,j;
+ mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
+ for (i = 1; (i <= stream->nmsgs) &&
+ !((SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE))->dirty; ++i);
+ if (i <= stream->nmsgs) { /* only update if some entry is dirty */
+ rewind (f); /* let's start at the very beginning */
+ /* write sequence */
+ fprintf (f,SEQFMT,LOCAL->sortcacheseq = mix_modseq(LOCAL->sortcacheseq));
+ for (i = 1; ret && (i <= stream->nmsgs); ++i) {
+ MESSAGECACHE *elt = mail_elt (stream,i);
+ SORTCACHE *s = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE);
+ STRINGLIST *sl;
+ s->dirty = NIL; /* no longer dirty */
+ if (sl = s->references) /* count length of references */
+ for (j = 1; sl && sl->text.data; sl = sl->next)
+ j += 10 + sl->text.size;
+ else j = 0; /* no references yet */
+ fprintf (f,SCRFMT,elt->private.uid,s->date,
+ s->from ? strlen (s->from) + 1 : 0,
+ s->to ? strlen (s->to) + 1 : 0,s->cc ? strlen (s->cc) + 1 : 0,
+ s->refwd ? 'R' : ' ',s->subject ? strlen (s->subject) + 1: 0,
+ s->message_id ? strlen (s->message_id) + 1 : 0,j);
+ if (s->from) fprintf (f,"F%s\015\012",s->from);
+ if (s->to) fprintf (f,"T%s\015\012",s->to);
+ if (s->cc) fprintf (f,"C%s\015\012",s->cc);
+ if (s->subject) fprintf (f,"S%s\015\012",s->subject);
+ if (s->message_id) fprintf (f,"M%s\015\012",s->message_id);
+ if (j) { /* any references to write? */
+ fputc ('R',f); /* yes, do so */
+ for (sl = s->references; sl && sl->text.data; sl = sl->next)
+ fprintf (f,"%08lx:%s:",sl->text.size,sl->text.data);
+ fputs ("\015\012",f);
+ }
+ if (ferror (f)) {
+ MM_LOG ("Error updating mix sortcache file",WARN);
+ ret = NIL;
+ }
+ }
+ if (ret && fflush (f)) {
+ MM_LOG ("Error flushing mix sortcache file",WARN);
+ ret = NIL;
+ }
+ if (ret) ftruncate (fileno (f),ftell (f));
+ }
+ if (fclose (f)) {
+ MM_LOG ("Error closing mix sortcache file",WARN);
+ ret = NIL;
+ }
+ }
+ return ret;
+}
+
+/* MIX generic file routines */
+
+/* MIX read record
+ * Accepts: open FILE
+ * buffer
+ * buffer length
+ * record type
+ * Returns: buffer if success, else NIL (zero-length buffer means EOF)
+ */
+
+char *mix_read_record (FILE *f,char *buf,unsigned long buflen,char *type)
+{
+ char *s,tmp[MAILTMPLEN];
+ /* ensure string tied off */
+ buf[buflen-2] = buf[buflen-1] = '\0';
+ while (fgets (buf,buflen-1,f)) {
+ if (s = strchr (buf,'\012')) {
+ if ((s != buf) && (s[-1] == '\015')) --s;
+ *s = '\0'; /* tie off buffer */
+ if (s != buf) return buf; /* return if non-empty buffer */
+ sprintf (tmp,"Empty mix %s record",type);
+ MM_LOG (tmp,WARN);
+ }
+ else if (buf[buflen-2]) { /* overlong record is bad news */
+ sprintf (tmp,"Oversize mix %s record: %.512s",type,buf);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else {
+ sprintf (tmp,"Truncated mix %s record: %.512s",type,buf);
+ MM_LOG (tmp,WARN);
+ return buf; /* pass to caller anyway */
+ }
+ }
+ buf[0] = '\0'; /* return empty buffer on EOF */
+ return buf;
+}
+
+/* MIX read sequence record
+ * Accepts: open FILE
+ * Returns: sequence value, or NIL if failure
+ */
+
+unsigned long mix_read_sequence (FILE *f)
+{
+ unsigned long ret;
+ char *s,tmp[MAILTMPLEN];
+ if (!mix_read_record (f,tmp,MAILTMPLEN-1,"sequence")) return NIL;
+ switch (tmp[0]) { /* examine record */
+ case '\0': /* end of file */
+ ret = 1; /* start a new sequence regime */
+ break;
+ case 'S': /* sequence record */
+ if (isxdigit (tmp[1])) { /* must be followed by hex value */
+ ret = strtoul (tmp+1,&s,16);
+ if (!*s) break; /* and nothing more */
+ }
+ /* drop into default case */
+ default: /* anything else is an error */
+ return NIL; /* return error */
+ }
+ return ret;
+}
+
+/* MIX internal routines */
+
+
+/* MIX mail build directory name
+ * Accepts: destination string
+ * source
+ * Returns: destination or empty string if error
+ */
+
+char *mix_dir (char *dst,char *name)
+{
+ char *s;
+ /* empty string if mailboxfile fails */
+ if (!mailboxfile (dst,name)) *dst = '\0';
+ /* driver-selected INBOX */
+ else if (!*dst) mailboxfile (dst,"~/INBOX");
+ /* tie off unnecessary trailing / */
+ else if ((s = strrchr (dst,'/')) && !s[1]) *s = '\0';
+ return dst;
+}
+
+
+/* MIX mail build file name
+ * Accepts: destination string
+ * directory name
+ * file name
+ * Returns: destination
+ */
+
+char *mix_file (char *dst,char *dir,char *name)
+{
+ sprintf (dst,"%.500s/%.80s%.80s",dir,MIXNAME,name);
+ return dst;
+}
+
+
+/* MIX mail build file name from data file number
+ * Accepts: destination string
+ * directory name
+ * data file number
+ * Returns: destination
+ */
+
+char *mix_file_data (char *dst,char *dir,unsigned long data)
+{
+ char tmp[MAILTMPLEN];
+ if (data) sprintf (tmp,"%08lx",data);
+ else tmp[0] = '\0'; /* compatibility with experimental version */
+ return mix_file (dst,dir,tmp);
+}
+
+/* MIX mail get new modseq
+ * Accepts: old modseq
+ * Returns: new modseq value
+ */
+
+unsigned long mix_modseq (unsigned long oldseq)
+{
+ /* normally time now */
+ unsigned long ret = (unsigned long) time (NIL);
+ /* ensure that modseq doesn't go backwards */
+ if (ret <= oldseq) ret = oldseq + 1;
+ return ret;
+}
diff --git a/imap/src/osdep/unix/mkauths b/imap/src/osdep/unix/mkauths
new file mode 100755
index 00000000..8e9cc0cf
--- /dev/null
+++ b/imap/src/osdep/unix/mkauths
@@ -0,0 +1,40 @@
+#!/bin/sh
+# ========================================================================
+# Copyright 1988-2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# ========================================================================
+
+# Program: Authenticator Linkage Generator
+#
+# Author: Mark Crispin
+# Networks and Distributed Computing
+# Computing & Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, WA 98195
+# Internet: MRC@CAC.Washington.EDU
+#
+# Date: 5 December 1995
+# Last Edited: 30 August 2006
+
+# Erase old authenticators list
+rm -f auths.c
+touch auths.c
+
+# Now define the new list
+for authenticator
+ do
+ if [ -f Makefile."$authenticator" ]; then
+ make -f Makefile."$authenticator" `cat SPECIALS`
+ fi
+ echo "extern AUTHENTICATOR auth_"$authenticator";" >> linkage.h
+ echo " auth_link (&auth_"$authenticator"); /* link in the $authenticator authenticator */" | cat >> linkage.c
+ echo "#include \"auth_"$authenticator".c\"" >> auths.c
+done
diff --git a/imap/src/osdep/unix/mmdf.c b/imap/src/osdep/unix/mmdf.c
new file mode 100644
index 00000000..e962434e
--- /dev/null
+++ b/imap/src/osdep/unix/mmdf.c
@@ -0,0 +1,2549 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MMDF mail routines
+ *
+ * Author: Mark Crispin
+ * UW Technology
+ * University of Washington
+ * Seattle, WA 98195
+ * Internet: MRC@Washington.EDU
+ *
+ * Date: 20 December 1989
+ * Last Edited: 27 March 2008
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <signal.h>
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <sys/stat.h>
+#include "pseudo.h"
+#include "fdstring.h"
+#include "misc.h"
+#include "dummy.h"
+
+/* Supposedly, this page has everything the MMDF driver needs to know about
+ * the MMDF delimiter. By changing these macros, the MMDF driver should
+ * change with it. Note that if you change the length of MMDFHDRTXT you
+ * also need to change the ISMMDF and RETIFMMDFWRD macros to reflect the new
+ * size.
+ */
+
+
+/* Useful MMDF constants */
+
+#define MMDFCHR '\01' /* MMDF character */
+#define MMDFCHRS 0x01010101 /* MMDF header character spread in a word */
+ /* MMDF header text */
+#define MMDFHDRTXT "\01\01\01\01\n"
+ /* length of MMDF header text */
+#define MMDFHDRLEN (sizeof (MMDFHDRTXT) - 1)
+
+
+/* Validate MMDF header
+ * Accepts: pointer to candidate string to validate as an MMDF header
+ * Returns: T if valid; else NIL
+ */
+
+#define ISMMDF(s) \
+ ((*(s) == MMDFCHR) && ((s)[1] == MMDFCHR) && ((s)[2] == MMDFCHR) && \
+ ((s)[3] == MMDFCHR) && ((s)[4] == '\n'))
+
+
+/* Return if a 32-bit word has the start of an MMDF header
+ * Accepts: pointer to word of four bytes to validate as an MMDF header
+ * Returns: pointer to MMDF header, else proceeds
+ */
+
+#define RETIFMMDFWRD(s) { \
+ if (s[3] == MMDFCHR) { \
+ if ((s[4] == MMDFCHR) && (s[5] == MMDFCHR) && (s[6] == MMDFCHR) && \
+ (s[7] == '\n')) return s + 3; \
+ else if (s[2] == MMDFCHR) { \
+ if ((s[4] == MMDFCHR) && (s[5] == MMDFCHR) && (s[6] == '\n')) \
+ return s + 2; \
+ else if (s[1] == MMDFCHR) { \
+ if ((s[4] == MMDFCHR) && (s[5] == '\n')) return s + 1; \
+ else if ((*s == MMDFCHR) && (s[4] == '\n')) return s; \
+ } \
+ } \
+ } \
+}
+
+/* Validate line
+ * Accepts: pointer to candidate string to validate as a From header
+ * return pointer to end of date/time field
+ * return pointer to offset from t of time (hours of ``mmm dd hh:mm'')
+ * return pointer to offset from t of time zone (if non-zero)
+ * Returns: t,ti,zn set if valid From string, else ti is NIL
+ */
+
+#define VALID(s,x,ti,zn) { \
+ ti = 0; \
+ if ((*s == 'F') && (s[1] == 'r') && (s[2] == 'o') && (s[3] == 'm') && \
+ (s[4] == ' ')) { \
+ for (x = s + 5; *x && *x != '\n'; x++); \
+ if (*x) { \
+ if (x - s >= 41) { \
+ for (zn = -1; x[zn] != ' '; zn--); \
+ if ((x[zn-1] == 'm') && (x[zn-2] == 'o') && (x[zn-3] == 'r') && \
+ (x[zn-4] == 'f') && (x[zn-5] == ' ') && (x[zn-6] == 'e') && \
+ (x[zn-7] == 't') && (x[zn-8] == 'o') && (x[zn-9] == 'm') && \
+ (x[zn-10] == 'e') && (x[zn-11] == 'r') && (x[zn-12] == ' '))\
+ x += zn - 12; \
+ } \
+ if (x - s >= 27) { \
+ if (x[-5] == ' ') { \
+ if (x[-8] == ':') zn = 0,ti = -5; \
+ else if (x[-9] == ' ') ti = zn = -9; \
+ else if ((x[-11] == ' ') && ((x[-10]=='+') || (x[-10]=='-'))) \
+ ti = zn = -11; \
+ } \
+ else if (x[-4] == ' ') { \
+ if (x[-9] == ' ') zn = -4,ti = -9; \
+ } \
+ else if (x[-6] == ' ') { \
+ if ((x[-11] == ' ') && ((x[-5] == '+') || (x[-5] == '-'))) \
+ zn = -6,ti = -11; \
+ } \
+ if (ti && !((x[ti - 3] == ':') && \
+ (x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') && \
+ (x[ti - 3] == ' ') && (x[ti - 7] == ' ') && \
+ (x[ti - 11] == ' '))) ti = 0; \
+ } \
+ } \
+ } \
+}
+
+/* You are not expected to understand this macro, but read the next page if
+ * you are not faint of heart.
+ *
+ * Known formats to the VALID macro are:
+ * From user Wed Dec 2 05:53 1992
+ * BSD From user Wed Dec 2 05:53:22 1992
+ * SysV From user Wed Dec 2 05:53 PST 1992
+ * rn From user Wed Dec 2 05:53:22 PST 1992
+ * From user Wed Dec 2 05:53 -0700 1992
+ * emacs From user Wed Dec 2 05:53:22 -0700 1992
+ * From user Wed Dec 2 05:53 1992 PST
+ * From user Wed Dec 2 05:53:22 1992 PST
+ * From user Wed Dec 2 05:53 1992 -0700
+ * Solaris From user Wed Dec 2 05:53:22 1992 -0700
+ *
+ * Plus all of the above with `` remote from xxx'' after it. Thank you very
+ * much, smail and Solaris, for making my life considerably more complicated.
+ */
+
+/*
+ * What? You want to understand the VALID macro anyway? Alright, since you
+ * insist. Actually, it isn't really all that difficult, provided that you
+ * take it step by step.
+ *
+ * Line 1 Initializes the return ti value to failure (0);
+ * Lines 2-3 Validates that the 1st-5th characters are ``From ''.
+ * Lines 4-5 Validates that there is an end of line and points x at it.
+ * Lines 6-13 First checks to see if the line is at least 41 characters long.
+ * If so, it scans backwards to find the rightmost space. From
+ * that point, it scans backwards to see if the string matches
+ * `` remote from''. If so, it sets x to point to the space at
+ * the start of the string.
+ * Line 14 Makes sure that there are at least 27 characters in the line.
+ * Lines 15-20 Checks if the date/time ends with the year (there is a space
+ * five characters back). If there is a colon three characters
+ * further back, there is no timezone field, so zn is set to 0
+ * and ti is set in front of the year. Otherwise, there must
+ * either to be a space four characters back for a three-letter
+ * timezone, or a space six characters back followed by a + or -
+ * for a numeric timezone; in either case, zn and ti become the
+ * offset of the space immediately before it.
+ * Lines 21-23 Are the failure case for line 14. If there is a space four
+ * characters back, it is a three-letter timezone; there must be a
+ * space for the year nine characters back. zn is the zone
+ * offset; ti is the offset of the space.
+ * Lines 24-27 Are the failure case for line 20. If there is a space six
+ * characters back, it is a numeric timezone; there must be a
+ * space eleven characters back and a + or - five characters back.
+ * zn is the zone offset; ti is the offset of the space.
+ * Line 28-31 If ti is valid, make sure that the string before ti is of the
+ * form www mmm dd hh:mm or www mmm dd hh:mm:ss, otherwise
+ * invalidate ti. There must be a colon three characters back
+ * and a space six or nine characters back (depending upon
+ * whether or not the character six characters back is a colon).
+ * There must be a space three characters further back (in front
+ * of the day), one seven characters back (in front of the month),
+ * and one eleven characters back (in front of the day of week).
+ * ti is set to be the offset of the space before the time.
+ *
+ * Why a macro? It gets invoked a *lot* in a tight loop. On some of the
+ * newer pipelined machines it is faster being open-coded than it would be if
+ * subroutines are called.
+ *
+ * Why does it scan backwards from the end of the line, instead of doing the
+ * much easier forward scan? There is no deterministic way to parse the
+ * ``user'' field, because it may contain unquoted spaces! Yes, I tested it to
+ * see if unquoted spaces were possible. They are, and I've encountered enough
+ * evil mail to be totally unwilling to trust that ``it will never happen''.
+ */
+
+/* Build parameters */
+
+#define KODRETRY 15 /* kiss-of-death retry in seconds */
+#define LOCKTIMEOUT 5 /* lock timeout in minutes */
+
+
+/* MMDF I/O stream local data */
+
+typedef struct mmdf_local {
+ unsigned int dirty : 1; /* disk copy needs updating */
+ unsigned int ddirty : 1; /* double-dirty, ping becomes checkpoint */
+ unsigned int pseudo : 1; /* uses a pseudo message */
+ unsigned int appending : 1; /* don't mark new messages as old */
+ int fd; /* mailbox file descriptor */
+ int ld; /* lock file descriptor */
+ char *lname; /* lock file name */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned long uid; /* current text uid */
+ SIZEDTEXT text; /* current text */
+ unsigned long textlen; /* current text length */
+ char *line; /* returned line */
+ char *linebuf; /* line readin buffer */
+ unsigned long linebuflen; /* current line readin buffer length */
+} MMDFLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MMDFLOCAL *) stream->local)
+
+
+/* MMDF protected file structure */
+
+typedef struct mmdf_file {
+ MAILSTREAM *stream; /* current stream */
+ off_t curpos; /* current file position */
+ off_t protect; /* protected position */
+ off_t filepos; /* current last written file position */
+ char *buf; /* overflow buffer */
+ size_t buflen; /* current overflow buffer length */
+ char *bufpos; /* current buffer position */
+} MMDFFILE;
+
+/* Function prototypes */
+
+DRIVER *mmdf_valid (char *name);
+long mmdf_isvalid (char *name,char *tmp);
+long mmdf_isvalid_fd (int fd,char *tmp);
+void *mmdf_parameters (long function,void *value);
+void mmdf_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void mmdf_list (MAILSTREAM *stream,char *ref,char *pat);
+void mmdf_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mmdf_create (MAILSTREAM *stream,char *mailbox);
+long mmdf_delete (MAILSTREAM *stream,char *mailbox);
+long mmdf_rename (MAILSTREAM *stream,char *old,char *newname);
+MAILSTREAM *mmdf_open (MAILSTREAM *stream);
+void mmdf_close (MAILSTREAM *stream,long options);
+char *mmdf_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long mmdf_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+char *mmdf_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
+ unsigned long *length,long flags);
+void mmdf_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long mmdf_ping (MAILSTREAM *stream);
+void mmdf_check (MAILSTREAM *stream);
+long mmdf_expunge (MAILSTREAM *stream,char *sequence,long options);
+long mmdf_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long mmdf_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+int mmdf_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg);
+int mmdf_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set);
+
+void mmdf_abort (MAILSTREAM *stream);
+char *mmdf_file (char *dst,char *name);
+int mmdf_lock (char *file,int flags,int mode,DOTLOCK *lock,int op);
+void mmdf_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock);
+int mmdf_parse (MAILSTREAM *stream,DOTLOCK *lock,int op);
+char *mmdf_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size);
+unsigned long mmdf_pseudo (MAILSTREAM *stream,char *hdr);
+unsigned long mmdf_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
+ unsigned long uid,long flag);
+long mmdf_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock,
+ long flags);
+long mmdf_extend (MAILSTREAM *stream,unsigned long size);
+void mmdf_write (MMDFFILE *f,char *s,unsigned long i);
+void mmdf_phys_write (MMDFFILE *f,char *buf,size_t size);
+
+/* MMDF mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mmdfdriver = {
+ "mmdf", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_LOCKING|DR_NONEWMAILRONLY|DR_XPOINT,
+ (DRIVER *) NIL, /* next driver */
+ mmdf_valid, /* mailbox is valid for us */
+ mmdf_parameters, /* manipulate parameters */
+ mmdf_scan, /* scan mailboxes */
+ mmdf_list, /* list mailboxes */
+ mmdf_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ mmdf_create, /* create mailbox */
+ mmdf_delete, /* delete mailbox */
+ mmdf_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ mmdf_open, /* open mailbox */
+ mmdf_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mmdf_header, /* fetch message header */
+ mmdf_text, /* fetch message text */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ mmdf_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mmdf_ping, /* ping mailbox to see if still alive */
+ mmdf_check, /* check for new messages */
+ mmdf_expunge, /* expunge deleted messages */
+ mmdf_copy, /* copy messages to another mailbox */
+ mmdf_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mmdfproto = {&mmdfdriver};
+
+char *mmdfhdr = MMDFHDRTXT; /* MMDF header */
+
+/* MMDF mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mmdf_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return mmdf_isvalid (name,tmp) ? &mmdfdriver : NIL;
+}
+
+
+/* MMDF mail test for valid mailbox name
+ * Accepts: mailbox name
+ * scratch buffer
+ * Returns: T if valid, NIL otherwise
+ */
+
+long mmdf_isvalid (char *name,char *tmp)
+{
+ int fd;
+ int ret = NIL;
+ char *t,file[MAILTMPLEN];
+ struct stat sbuf;
+ time_t tp[2];
+ errno = EINVAL; /* assume invalid argument */
+ /* must be non-empty file */
+ if ((t = dummy_file (file,name)) && !stat (t,&sbuf)) {
+ if (!sbuf.st_size)errno = 0;/* empty file */
+ else if ((fd = open (file,O_RDONLY,NIL)) >= 0) {
+ /* error -1 for invalid format */
+ if (!(ret = mmdf_isvalid_fd (fd,tmp))) errno = -1;
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) {
+ tp[0] = sbuf.st_atime; /* preserve atime and mtime */
+ tp[1] = sbuf.st_mtime;
+ utime (file,tp); /* set the times */
+ }
+ }
+ }
+ return ret; /* return what we should */
+}
+
+/* MMDF mail test for valid mailbox
+ * Accepts: file descriptor
+ * scratch buffer
+ * Returns: T if valid, NIL otherwise
+ */
+
+long mmdf_isvalid_fd (int fd,char *tmp)
+{
+ int ret = NIL;
+ memset (tmp,'\0',MAILTMPLEN);
+ if (read (fd,tmp,MAILTMPLEN-1) >= 0) ret = ISMMDF (tmp) ? T : NIL;
+ return ret; /* return what we should */
+}
+
+
+/* MMDF manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mmdf_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = dummy_file ((char *) value,"INBOX");
+ break;
+ }
+ return ret;
+}
+
+/* MMDF mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mmdf_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MMDF mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mmdf_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MMDF mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mmdf_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MMDF mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mmdf_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN];
+ long ret = NIL;
+ int i,fd;
+ time_t ti = time (0);
+ if (!(s = dummy_file (mbx,mailbox))) {
+ sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
+ MM_LOG (tmp,ERROR);
+ }
+ /* create underlying file */
+ else if (dummy_create_path (stream,s,get_dir_protection (mailbox))) {
+ /* done if dir-only or whiner */
+ if (((s = strrchr (s,'/')) && !s[1]) ||
+ mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) ret = T;
+ else if ((fd = open (mbx,O_WRONLY,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0) {
+ sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else { /* initialize header */
+ memset (tmp,'\0',MAILTMPLEN);
+ sprintf (tmp,"%sFrom %s %sDate: ",mmdfhdr,pseudo_from,ctime (&ti));
+ rfc822_date (s = tmp + strlen (tmp));
+ sprintf (s += strlen (s), /* write the pseudo-header */
+ "\nFrom: %s <%s@%s>\nSubject: %s\nX-IMAP: %010lu 0000000000",
+ pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
+ (unsigned long) ti);
+ for (i = 0; i < NUSERFLAGS; ++i) if (default_user_flag (i))
+ sprintf (s += strlen (s)," %s",default_user_flag (i));
+ sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n%s",pseudo_msg,mmdfhdr);
+ if (write (fd,tmp,strlen (tmp)) > 0) ret = T;
+ else {
+ sprintf (tmp,"Can't initialize mailbox node %.80s: %s",mbx,
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ close (fd); /* close file */
+ }
+ }
+ /* set proper protections */
+ return ret ? set_mbx_protections (mailbox,mbx) : NIL;
+}
+
+/* MMDF mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mmdf_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return mmdf_rename (stream,mailbox,NIL);
+}
+
+
+/* MMDF mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long mmdf_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = NIL;
+ char c,*s = NIL;
+ char tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ DOTLOCK lockx;
+ int fd,ld;
+ long i;
+ struct stat sbuf;
+ MM_CRITICAL (stream); /* get the c-client lock */
+ if (!dummy_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'/')) && !s[1]))))
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ /* lock out other c-clients */
+ else if ((ld = lockname (lock,file,LOCK_EX|LOCK_NB,&i)) < 0)
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+
+ else {
+ if ((fd = mmdf_lock (file,O_RDWR,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lockx,LOCK_EX)) < 0)
+ sprintf (tmp,"Can't lock mailbox %.80s: %s",old,strerror (errno));
+ else {
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if (s = strrchr (s,'/')) {
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp,get_dir_protection (newname))) {
+ mmdf_unlock (fd,NIL,&lockx);
+ mmdf_unlock (ld,NIL,NIL);
+ unlink (lock);
+ MM_NOCRITICAL (stream);
+ return ret; /* return success or failure */
+ }
+ *s = c; /* restore full name */
+ }
+ if (rename (file,tmp))
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ else ret = T; /* set success */
+ }
+ else if (unlink (file))
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ else ret = T; /* set success */
+ mmdf_unlock (fd,NIL,&lockx);
+ }
+ mmdf_unlock (ld,NIL,NIL); /* flush the lock */
+ unlink (lock);
+ }
+ MM_NOCRITICAL (stream); /* no longer critical */
+ if (!ret) MM_LOG (tmp,ERROR); /* log error */
+ return ret; /* return success or failure */
+}
+
+/* MMDF mail open
+ * Accepts: Stream to open
+ * Returns: Stream on success, NIL on failure
+ */
+
+MAILSTREAM *mmdf_open (MAILSTREAM *stream)
+{
+ long i;
+ int fd;
+ char tmp[MAILTMPLEN];
+ DOTLOCK lock;
+ long retry;
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&mmdfproto);
+ retry = stream->silent ? 1 : KODRETRY;
+ if (stream->local) fatal ("mmdf recycle stream");
+ stream->local = memset (fs_get (sizeof (MMDFLOCAL)),0,sizeof (MMDFLOCAL));
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ /* canonicalize the stream mailbox name */
+ if (!dummy_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* flush old name */
+ fs_give ((void **) &stream->mailbox);
+ /* save canonical name */
+ stream->mailbox = cpystr (tmp);
+ LOCAL->fd = LOCAL->ld = -1; /* no file or state locking yet */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE);
+ LOCAL->text.size = CHUNKSIZE - 1;
+ LOCAL->linebuf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->linebuflen = CHUNKSIZE - 1;
+ stream->sequence++; /* bump sequence number */
+
+ /* make lock for read/write access */
+ if (!stream->rdonly) while (retry) {
+ /* try to lock file */
+ if ((fd = lockname (tmp,stream->mailbox,LOCK_EX|LOCK_NB,&i)) < 0) {
+ /* suppressing kiss-of-death? */
+ if (stream->nokod) retry = 0;
+ /* no, first time through? */
+ else if (retry-- == KODRETRY) {
+ /* learned other guy's PID and can signal? */
+ if (i && !kill ((int) i,SIGUSR2)) {
+ sprintf (tmp,"Trying to get mailbox lock from process %ld",i);
+ MM_LOG (tmp,WARN);
+ }
+ else retry = 0; /* give up */
+ }
+ if (!stream->silent) { /* nothing if silent stream */
+ if (retry) sleep (1); /* wait a second before trying again */
+ else MM_LOG ("Mailbox is open by another process, access is readonly",
+ WARN);
+ }
+ }
+ else { /* got the lock, nobody else can alter state */
+ LOCAL->ld = fd; /* note lock's fd and name */
+ LOCAL->lname = cpystr (tmp);
+ /* make sure mode OK (don't use fchmod()) */
+ chmod (LOCAL->lname,(long) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
+ if (stream->silent) i = 0;/* silent streams won't accept KOD */
+ else { /* note our PID in the lock */
+ sprintf (tmp,"%d",getpid ());
+ write (fd,tmp,(i = strlen (tmp))+1);
+ }
+ ftruncate (fd,i); /* make sure tied off */
+ fsync (fd); /* make sure it's available */
+ retry = 0; /* no more need to try */
+ }
+ }
+
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ /* will we be able to get write access? */
+ if ((LOCAL->ld >= 0) && access (stream->mailbox,W_OK) && (errno == EACCES)) {
+ MM_LOG ("Can't get write access to mailbox, access is readonly",WARN);
+ flock (LOCAL->ld,LOCK_UN); /* release the lock */
+ close (LOCAL->ld); /* close the lock file */
+ LOCAL->ld = -1; /* no more lock fd */
+ unlink (LOCAL->lname); /* delete it */
+ }
+ /* reset UID validity */
+ stream->uid_validity = stream->uid_last = 0;
+ if (stream->silent && !stream->rdonly && (LOCAL->ld < 0))
+ mmdf_abort (stream); /* abort if can't get RW silent stream */
+ /* parse mailbox */
+ else if (mmdf_parse (stream,&lock,LOCK_SH)) {
+ mmdf_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream); /* done with critical */
+ }
+ if (!LOCAL) return NIL; /* failure if stream died */
+ /* make sure upper level knows readonly */
+ stream->rdonly = (LOCAL->ld < 0);
+ /* notify about empty mailbox */
+ if (!(stream->nmsgs || stream->silent)) MM_LOG ("Mailbox is empty",NIL);
+ if (!stream->rdonly) { /* flags stick if readwrite */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft = T;
+ if (!stream->uid_nosticky) {/* users with lives get permanent keywords */
+ stream->perm_user_flags = 0xffffffff;
+ /* and maybe can create them too! */
+ stream->kwd_create = stream->user_flags[NUSERFLAGS-1] ? NIL : T;
+ }
+ }
+ return stream; /* return stream alive to caller */
+}
+
+
+/* MMDF mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mmdf_close (MAILSTREAM *stream,long options)
+{
+ int silent = stream->silent;
+ stream->silent = T; /* go silent */
+ /* expunge if requested */
+ if (options & CL_EXPUNGE) mmdf_expunge (stream,NIL,NIL);
+ /* else dump final checkpoint */
+ else if (LOCAL->dirty) mmdf_check (stream);
+ stream->silent = silent; /* restore old silence state */
+ mmdf_abort (stream); /* now punt the file and local data */
+}
+
+/* MMDF mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+ /* lines to filter from header */
+static STRINGLIST *mmdf_hlines = NIL;
+
+char *mmdf_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned char *s,*t,*tl;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get cache */
+ if (!mmdf_hlines) { /* once only code */
+ STRINGLIST *lines = mmdf_hlines = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "Status"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-Status"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-Keywords"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-UID"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-IMAP"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-IMAPbase"));
+ }
+ /* go to header position */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.msg.header.offset,L_SET);
+
+ if (flags & FT_INTERNAL) { /* initial data OK? */
+ if (elt->private.msg.header.text.size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
+ elt->private.msg.header.text.size) + 1);
+ }
+ /* read message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.header.text.size);
+ /* got text, tie off string */
+ LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0';
+ /* squeeze out CRs (in case from PC) */
+ for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
+ if (*t != '\r') *s++ = *t;
+ *s = '\0';
+ *length = s - LOCAL->buf; /* adjust length */
+ }
+ else { /* need to make a CRLF version */
+ read (LOCAL->fd,s = (char *) fs_get (elt->private.msg.header.text.size+1),
+ elt->private.msg.header.text.size);
+ /* tie off string, and convert to CRLF */
+ s[elt->private.msg.header.text.size] = '\0';
+ *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s,
+ elt->private.msg.header.text.size);
+ fs_give ((void **) &s); /* free readin buffer */
+ /* squeeze out spurious CRs */
+ for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
+ if ((*t != '\r') || (t[1] == '\n')) *s++ = *t;
+ *s = '\0';
+ *length = s - LOCAL->buf; /* adjust length */
+ }
+ *length = mail_filter (LOCAL->buf,*length,mmdf_hlines,FT_NOT);
+ return (char *) LOCAL->buf; /* return processed copy */
+}
+
+/* MMDF mail fetch message text
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL if failure
+ */
+
+long mmdf_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ char *s;
+ unsigned long i;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);/* get cache element */
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ /* mark message seen and dirty */
+ elt->seen = elt->private.dirty = LOCAL->dirty = T;
+ MM_FLAGS (stream,msgno);
+ }
+ s = mmdf_text_work (stream,elt,&i,flags);
+ INIT (bs,mail_string,s,i); /* set up stringstruct */
+ return T; /* success */
+}
+
+/* MMDF mail fetch message text worker routine
+ * Accepts: MAIL stream
+ * message cache element
+ * pointer to returned header text length
+ * option flags
+ */
+
+char *mmdf_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
+ unsigned long *length,long flags)
+{
+ FDDATA d;
+ STRING bs;
+ unsigned char c,*s,*t,*tl,tmp[CHUNKSIZE];
+ /* go to text position */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.msg.text.offset,L_SET);
+ if (flags & FT_INTERNAL) { /* initial data OK? */
+ if (elt->private.msg.text.text.size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
+ elt->private.msg.text.text.size) + 1);
+ }
+ /* read message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size);
+ /* got text, tie off string */
+ LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0';
+ /* squeeze out CRs (in case from PC) */
+ for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
+ if (*t != '\r') *s++ = *t;
+ *s = '\0';
+ *length = s - LOCAL->buf; /* adjust length */
+ return (char *) LOCAL->buf;
+ }
+
+ /* have it cached already? */
+ if (elt->private.uid != LOCAL->uid) {
+ /* not cached, cache it now */
+ LOCAL->uid = elt->private.uid;
+ /* is buffer big enough? */
+ if (elt->rfc822_size > LOCAL->text.size) {
+ /* excessively conservative, but the right thing is too hard to do */
+ fs_give ((void **) &LOCAL->text.data);
+ LOCAL->text.data = (unsigned char *)
+ fs_get ((LOCAL->text.size = elt->rfc822_size) + 1);
+ }
+ d.fd = LOCAL->fd; /* yes, set up file descriptor */
+ d.pos = elt->private.special.offset + elt->private.msg.text.offset;
+ d.chunk = tmp; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* file chunk size */
+ INIT (&bs,fd_string,&d,elt->private.msg.text.text.size);
+ for (s = (char *) LOCAL->text.data; SIZE (&bs);) switch (c = SNX (&bs)) {
+ case '\r': /* carriage return seen */
+ break;
+ case '\n':
+ *s++ = '\r'; /* insert a CR */
+ default:
+ *s++ = c; /* copy characters */
+ }
+ *s = '\0'; /* tie off buffer */
+ /* calculate length of cached data */
+ LOCAL->textlen = s - LOCAL->text.data;
+ }
+ *length = LOCAL->textlen; /* return from cache */
+ return (char *) LOCAL->text.data;
+}
+
+/* MMDF per-message modify flag
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void mmdf_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ /* only after finishing */
+ if (elt->valid) elt->private.dirty = LOCAL->dirty = T;
+}
+
+
+/* MMDF mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long mmdf_ping (MAILSTREAM *stream)
+{
+ DOTLOCK lock;
+ struct stat sbuf;
+ long reparse;
+ /* big no-op if not readwrite */
+ if (LOCAL && (LOCAL->ld >= 0) && !stream->lock) {
+ if (stream->rdonly) { /* does he want to give up readwrite? */
+ /* checkpoint if we changed something */
+ if (LOCAL->dirty) mmdf_check (stream);
+ flock (LOCAL->ld,LOCK_UN);/* release readwrite lock */
+ close (LOCAL->ld); /* close the readwrite lock file */
+ LOCAL->ld = -1; /* no more readwrite lock fd */
+ unlink (LOCAL->lname); /* delete the readwrite lock file */
+ }
+ else { /* see if need to reparse */
+ if (!(reparse = (long) mail_parameters (NIL,GET_NETFSSTATBUG,NIL))) {
+ /* get current mailbox size */
+ if (LOCAL->fd >= 0) fstat (LOCAL->fd,&sbuf);
+ else if (stat (stream->mailbox,&sbuf)) {
+ sprintf (LOCAL->buf,"Mailbox stat failed, aborted: %s",
+ strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ mmdf_abort (stream);
+ return NIL;
+ }
+ reparse = (sbuf.st_size != LOCAL->filesize);
+ }
+ /* parse if mailbox changed */
+ if ((LOCAL->ddirty || reparse) && mmdf_parse (stream,&lock,LOCK_EX)) {
+ /* force checkpoint if double-dirty */
+ if (LOCAL->ddirty) mmdf_rewrite (stream,NIL,&lock,NIL);
+ /* unlock mailbox */
+ else mmdf_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* and stream */
+ /* done with critical */
+ MM_NOCRITICAL (stream);
+ }
+ }
+ }
+ return LOCAL ? LONGT : NIL; /* return if still alive */
+}
+
+/* MMDF mail check mailbox
+ * Accepts: MAIL stream
+ */
+
+void mmdf_check (MAILSTREAM *stream)
+{
+ DOTLOCK lock;
+ /* parse and lock mailbox */
+ if (LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
+ mmdf_parse (stream,&lock,LOCK_EX)) {
+ /* any unsaved changes? */
+ if (LOCAL->dirty && mmdf_rewrite (stream,NIL,&lock,NIL)) {
+ if (!stream->silent) MM_LOG ("Checkpoint completed",NIL);
+ }
+ /* no checkpoint needed, just unlock */
+ else mmdf_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* unlock the stream */
+ MM_NOCRITICAL (stream); /* done with critical */
+ }
+}
+
+
+/* MMDF mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long mmdf_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ unsigned long i;
+ DOTLOCK lock;
+ char *msg = NIL;
+ if (ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
+ mmdf_parse (stream,&lock,LOCK_EX)) {
+ /* check expunged messages if not dirty */
+ for (i = 1; !LOCAL->dirty && (i <= stream->nmsgs); i++) {
+ MESSAGECACHE *elt = mail_elt (stream,i);
+ if (mail_elt (stream,i)->deleted) LOCAL->dirty = T;
+ }
+ if (!LOCAL->dirty) { /* not dirty and no expunged messages */
+ mmdf_unlock (LOCAL->fd,stream,&lock);
+ msg = "No messages deleted, so no update needed";
+ }
+ else if (mmdf_rewrite (stream,&i,&lock,sequence ? LONGT : NIL)) {
+ if (i) sprintf (msg = LOCAL->buf,"Expunged %lu messages",i);
+ else msg = "Mailbox checkpointed, but no messages expunged";
+ }
+ /* rewrite failed */
+ else mmdf_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* unlock the stream */
+ MM_NOCRITICAL (stream); /* done with critical */
+ if (msg && !stream->silent) MM_LOG (msg,NIL);
+ }
+ else if (!stream->silent)
+ MM_LOG ("Expunge ignored on readonly mailbox",WARN);
+ return ret;
+}
+
+/* MMDF mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long mmdf_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ int fd;
+ char *s,file[MAILTMPLEN];
+ DOTLOCK lock;
+ time_t tp[2];
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ long ret = T;
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ copyuid_t cu = (copyuid_t) (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ?
+ NIL : mail_parameters (NIL,GET_COPYUID,NIL));
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ MAILSTREAM *tstream = NIL;
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* make sure destination is valid */
+ if (!(mmdf_valid (mailbox) || !errno))
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ }
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ mmdf_create (NIL,"INBOX");/* create empty INBOX */
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid MMDF-format mailbox name: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a MMDF-format mailbox: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+
+ /* try to open rewrite for UIDPLUS */
+ if ((tstream = mail_open_work (&mmdfdriver,NIL,mailbox,
+ OP_SILENT|OP_NOKOD)) && tstream->rdonly)
+ tstream = mail_close (tstream);
+ if (cu && !tstream) { /* wanted a COPYUID? */
+ sprintf (LOCAL->buf,"Unable to write-open mailbox for COPYUID: %.80s",
+ mailbox);
+ MM_LOG (LOCAL->buf,WARN);
+ cu = NIL; /* don't try to do COPYUID */
+ }
+ LOCAL->buf[0] = '\0';
+ MM_CRITICAL (stream); /* go critical */
+ if ((fd = mmdf_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lock,LOCK_EX)) < 0) {
+ MM_NOCRITICAL (stream); /* done with critical */
+ sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR); /* log the error */
+ return NIL; /* failed */
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ /* write all requested messages to mailbox */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
+ if (write (fd,LOCAL->buf,elt->private.special.text.size) < 0) ret = NIL;
+ else { /* internal header succeeded */
+ s = mmdf_header (stream,i,&j,FT_INTERNAL);
+ /* header size, sans trailing newline */
+ if (j && (s[j - 2] == '\n')) j--;
+ if (write (fd,s,j) < 0) ret = NIL;
+ else { /* message header succeeded */
+ j = tstream ? /* write UIDPLUS data if have readwrite */
+ mmdf_xstatus (stream,LOCAL->buf,elt,++(tstream->uid_last),LONGT) :
+ mmdf_xstatus (stream,LOCAL->buf,elt,NIL,NIL);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ else { /* message status succeeded */
+ s = mmdf_text_work (stream,elt,&j,FT_INTERNAL);
+ if ((write (fd,s,j) < 0) || (write (fd,mmdfhdr,MMDFHDRLEN) < 0))
+ ret = NIL;
+ else if (cu) { /* need to pass back new UID? */
+ mail_append_set (source,mail_uid (stream,i));
+ mail_append_set (dest,tstream->uid_last);
+ }
+ }
+ }
+ }
+ }
+
+ if (!ret || fsync (fd)) { /* force out the update */
+ sprintf (LOCAL->buf,"Message copy failed: %s",strerror (errno));
+ ftruncate (fd,sbuf.st_size);
+ ret = NIL;
+ }
+ /* force UIDVALIDITY assignment now */
+ if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0);
+ /* return sets if doing COPYUID */
+ if (cu && ret) (*cu) (stream,mailbox,tstream->uid_validity,source,dest);
+ else { /* flush any sets we may have built */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ tp[1] = time (0); /* set mtime to now */
+ if (ret) tp[0] = tp[1] - 1; /* set atime to now-1 if successful copy */
+ else tp[0] = /* else preserve \Marked status */
+ ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
+ sbuf.st_atime : tp[1];
+ utime (file,tp); /* set the times */
+ mmdf_unlock (fd,NIL,&lock); /* unlock and close mailbox */
+ if (tstream) { /* update last UID if we can */
+ MMDFLOCAL *local = (MMDFLOCAL *) tstream->local;
+ local->dirty = T; /* do a rewrite */
+ local->appending = T; /* but not at the cost of marking as old */
+ tstream = mail_close (tstream);
+ }
+ /* log the error */
+ if (!ret) MM_LOG (LOCAL->buf,ERROR);
+ /* delete if requested message */
+ else if (options & CP_MOVE) for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence)
+ elt->deleted = elt->private.dirty = LOCAL->dirty = T;
+ MM_NOCRITICAL (stream); /* release critical */
+ return ret;
+}
+
+/* MMDF mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+#define BUFLEN 8*MAILTMPLEN
+
+long mmdf_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd;
+ unsigned long i;
+ char *flags,*date,buf[BUFLEN],tmp[MAILTMPLEN],file[MAILTMPLEN];
+ time_t tp[2];
+ FILE *sf,*df;
+ MESSAGECACHE elt;
+ DOTLOCK lock;
+ STRING *message;
+ unsigned long uidlocation = 0;
+ appenduid_t au = (appenduid_t)
+ (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ? NIL :
+ mail_parameters (NIL,GET_APPENDUID,NIL));
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ long ret = LONGT;
+ MAILSTREAM *tstream = NIL;
+ /* default stream to prototype */
+ if (!stream) { /* stream specified? */
+ stream = &mmdfproto; /* no, default stream to prototype */
+ for (i = 0; i < NUSERFLAGS && stream->user_flags[i]; ++i)
+ fs_give ((void **) &stream->user_flags[i]);
+ }
+ if (!mmdf_valid (mailbox)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ mmdf_create (NIL,"INBOX"); /* create empty INBOX */
+ case 0: /* merely empty file? */
+ tstream = stream;
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid MMDF-format mailbox name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MMDF-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get sniffing stream for keywords */
+ else if (!(tstream = mail_open (NIL,mailbox,
+ OP_READONLY|OP_SILENT|OP_NOKOD|OP_SNIFF))) {
+ sprintf (tmp,"Unable to examine mailbox for APPEND: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+
+ /* get first message */
+ if (!MM_APPEND (af) (tstream,data,&flags,&date,&message)) return NIL;
+ if (!(sf = tmpfile ())) { /* must have scratch file */
+ sprintf (tmp,".%lx.%lx",(unsigned long) time (0),(unsigned long)getpid ());
+ if (!stat (tmp,&sbuf) || !(sf = fopen (tmp,"wb+"))) {
+ sprintf (tmp,"Unable to create scratch file: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ unlink (tmp);
+ }
+ do { /* parse date */
+ if (!date) rfc822_date (date = tmp);
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ }
+ else { /* user wants to suppress time zones? */
+ if (mail_parameters (NIL,GET_NOTIMEZONES,NIL)) {
+ time_t when = mail_longdate (&elt);
+ date = ctime (&when); /* use traditional date */
+ }
+ /* use POSIX-style date */
+ else date = mail_cdate (tmp,&elt);
+ if (!SIZE (message)) MM_LOG ("Append of zero-length message",ERROR);
+ else if (!mmdf_collect_msg (tstream,sf,flags,date,message)) {
+ sprintf (tmp,"Error writing scratch file: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ /* get next message */
+ else if (MM_APPEND (af) (tstream,data,&flags,&date,&message)) continue;
+ }
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ } while (message); /* until no more messages */
+ if (fflush (sf)) {
+ sprintf (tmp,"Error finishing scratch file: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ }
+ i = ftell (sf); /* size of scratch file */
+ if (tstream != stream) tstream = mail_close (tstream);
+
+ MM_CRITICAL (stream); /* go critical */
+ /* try to open readwrite for UIDPLUS */
+ if ((tstream = mail_open_work (&mmdfdriver,NIL,mailbox,
+ OP_SILENT|OP_NOKOD)) && tstream->rdonly)
+ tstream = mail_close (tstream);
+ if (au && !tstream) { /* wanted an APPENDUID? */
+ sprintf (tmp,"Unable to re-open mailbox for APPENDUID: %.80s",mailbox);
+ MM_LOG (tmp,WARN);
+ au = NIL;
+ }
+ if (((fd = mmdf_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lock,LOCK_EX)) < 0) ||
+ !(df = fdopen (fd,"ab"))) {
+ MM_NOCRITICAL (stream); /* done with critical */
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ rewind (sf);
+ tp[1] = time (0); /* set mtime to now */
+ /* write all messages */
+ if (!mmdf_append_msgs (tstream,sf,df,au ? dst : NIL) ||
+ (fflush (df) == EOF) || fsync (fd)) {
+ sprintf (buf,"Message append failed: %s",strerror (errno));
+ MM_LOG (buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ tp[0] = /* preserve \Marked status */
+ ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
+ sbuf.st_atime : tp[1];
+ ret = NIL; /* return error */
+ }
+ else tp[0] = tp[1] - 1; /* set atime to now-1 if successful copy */
+ utime (file,tp); /* set the times */
+ fclose (sf); /* done with scratch file */
+ /* force UIDVALIDITY assignment now */
+ if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0);
+ /* return sets if doing APPENDUID */
+ if (au && ret) (*au) (mailbox,tstream->uid_validity,dst);
+ else mail_free_searchset (&dst);
+ mmdf_unlock (fd,NIL,&lock); /* unlock and close mailbox */
+ fclose (df);
+ if (tstream) { /* update last UID if we can */
+ MMDFLOCAL *local = (MMDFLOCAL *) tstream->local;
+ local->dirty = T; /* do a rewrite */
+ local->appending = T; /* but not at the cost of marking as old */
+ tstream = mail_close (tstream);
+ }
+ MM_NOCRITICAL (stream); /* release critical */
+ return ret;
+}
+
+/* Collect and write single message to append scratch file
+ * Accepts: MAIL stream
+ * scratch file
+ * flags
+ * date
+ * message stringstruct
+ * Returns: NIL if write error, else T
+ */
+
+int mmdf_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg)
+{
+ unsigned char *s,*t;
+ unsigned long uf;
+ long f = mail_parse_flags (stream,flags,&uf);
+ /* write metadata, note date ends with NL */
+ if (fprintf (sf,"%ld %lu %s",f,SIZE (msg) + 1,date) < 0) return NIL;
+ while (uf) /* write user flags */
+ if ((s = stream->user_flags[find_rightmost_bit (&uf)]) &&
+ (fprintf (sf," %s",s) < 0)) return NIL;
+ if (putc ('\n',sf) == EOF) return NIL;
+ while (SIZE (msg)) { /* copy text to scratch file */
+ for (s = (unsigned char *) msg->curpos, t = s + msg->cursize; s < t; ++s)
+ if (!*s) *s = 0x80; /* disallow NUL */
+ /* write buffered text */
+ if (fwrite (msg->curpos,1,msg->cursize,sf) == msg->cursize)
+ SETPOS (msg,GETPOS (msg) + msg->cursize);
+ else return NIL; /* failed */
+ }
+ /* write trailing newline and return */
+ return (putc ('\n',sf) == EOF) ? NIL : T;
+}
+
+/* Append messages from scratch file to mailbox
+ * Accepts: MAIL stream
+ * source file
+ * destination file
+ * uidset to update if non-NIL
+ * Returns: T if success, NIL if failure
+ */
+
+int mmdf_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set)
+{
+ int c;
+ long f;
+ unsigned long i,j;
+ char *x,tmp[MAILTMPLEN];
+ int hdrp = T;
+ /* get message metadata line */
+ while (fgets (tmp,MAILTMPLEN,sf)) {
+ if (!(isdigit (tmp[0]) && strchr (tmp,'\n'))) return NIL;
+ f = strtol (tmp,&x,10); /* get flags */
+ if (!((*x++ == ' ') && isdigit (*x))) return NIL;
+ i = strtoul (x,&x,10); /* get message size */
+ if ((*x++ != ' ') || /* build initial header */
+ (fprintf (df,"%sFrom %s@%s %sStatus: ",mmdfhdr,myusername(),
+ mylocalhost(),x) < 0) ||
+ (f&fSEEN && (putc ('R',df) == EOF)) ||
+ (fputs ("\nX-Status: ",df) == EOF) ||
+ (f&fDELETED && (putc ('D',df) == EOF)) ||
+ (f&fFLAGGED && (putc ('F',df) == EOF)) ||
+ (f&fANSWERED && (putc ('A',df) == EOF)) ||
+ (f&fDRAFT && (putc ('T',df) == EOF)) ||
+ (fputs ("\nX-Keywords:",df) == EOF)) return NIL;
+ /* copy keywords */
+ while ((c = getc (sf)) != '\n') switch (c) {
+ case EOF:
+ return NIL;
+ default:
+ if (putc (c,df) == EOF) return NIL;
+ }
+ if ((putc ('\n',df) == EOF) ||
+ (set && (fprintf (df,"X-UID: %lu\n",++(stream->uid_last)) < 0)))
+ return NIL;
+
+ for (c = '\n'; i && fgets (tmp,MAILTMPLEN,sf); c = tmp[j-1]) {
+ /* get read line length */
+ if (i < (j = strlen (tmp))) fatal ("mmdf_append_msgs overrun");
+ i -= j; /* number of bytes left */
+ /* squish out ^A and CRs (note copies NUL) */
+ for (x = tmp; x = strpbrk (x,"\01\r"); --j) memmove (x,x+1,j-(x-tmp));
+ if (!j) continue; /* do nothing if line emptied */
+ /* start of line? */
+ if ((c == '\n')) switch (tmp[0]) {
+ case 'S': case 's': /* possible "Status:" */
+ if (hdrp && (j > 6) && ((tmp[1] == 't') || (tmp[1] == 'T')) &&
+ ((tmp[2] == 'a') || (tmp[2] == 'A')) &&
+ ((tmp[3] == 't') || (tmp[3] == 'T')) &&
+ ((tmp[4] == 'u') || (tmp[4] == 'U')) &&
+ ((tmp[5] == 's') || (tmp[5] == 'S')) && (tmp[6] == ':') &&
+ (fputs ("X-Original-",df) == EOF)) return NIL;
+ break;
+ case 'X': case 'x': /* possible X-??? header */
+ if (hdrp && (tmp[1] == '-') &&
+ /* possible X-UID: */
+ (((j > 5) && ((tmp[2] == 'U') || (tmp[2] == 'u')) &&
+ ((tmp[3] == 'I') || (tmp[3] == 'i')) &&
+ ((tmp[4] == 'D') || (tmp[4] == 'd')) && (tmp[5] == ':')) ||
+ /* possible X-IMAP: */
+ ((j > 6) && ((tmp[2] == 'I') || (tmp[2] == 'i')) &&
+ ((tmp[3] == 'M') || (tmp[3] == 'm')) &&
+ ((tmp[4] == 'A') || (tmp[4] == 'a')) &&
+ ((tmp[5] == 'P') || (tmp[5] == 'p')) &&
+ ((tmp[6] == ':') ||
+ /* or X-IMAPbase: */
+ ((j > 10) && ((tmp[6] == 'b') || (tmp[6] == 'B')) &&
+ ((tmp[7] == 'a') || (tmp[7] == 'A')) &&
+ ((tmp[8] == 's') || (tmp[8] == 'S')) &&
+ ((tmp[9] == 'e') || (tmp[9] == 'E')) && (tmp[10] == ':')))) ||
+ /* possible X-Status: */
+ ((j > 8) && ((tmp[2] == 'S') || (tmp[2] == 's')) &&
+ ((tmp[3] == 't') || (tmp[3] == 'T')) &&
+ ((tmp[4] == 'a') || (tmp[4] == 'A')) &&
+ ((tmp[5] == 't') || (tmp[5] == 'T')) &&
+ ((tmp[6] == 'u') || (tmp[6] == 'U')) &&
+ ((tmp[7] == 's') || (tmp[7] == 'S')) && (tmp[8] == ':')) ||
+ /* possible X-Keywords: */
+ ((j > 10) && ((tmp[2] == 'K') || (tmp[2] == 'k')) &&
+ ((tmp[3] == 'e') || (tmp[3] == 'E')) &&
+ ((tmp[4] == 'y') || (tmp[4] == 'Y')) &&
+ ((tmp[5] == 'w') || (tmp[5] == 'W')) &&
+ ((tmp[6] == 'o') || (tmp[6] == 'O')) &&
+ ((tmp[7] == 'r') || (tmp[7] == 'R')) &&
+ ((tmp[8] == 'd') || (tmp[8] == 'D')) &&
+ ((tmp[9] == 's') || (tmp[9] == 'S')) && (tmp[10] == ':'))) &&
+ (fputs ("X-Original-",df) == EOF)) return NIL;
+ break;
+ case '\n': /* blank line */
+ hdrp = NIL;
+ break;
+ default: /* nothing to do */
+ break;
+ }
+ /* just write the line */
+ if (fwrite (tmp,1,j,df) != j) return NIL;
+ }
+ /* make sure read entire msg & wrote trailer */
+ if (i || (fputs (mmdfhdr,df) == EOF)) return NIL;
+ /* update set */
+ if (stream) mail_append_set (set,stream->uid_last);
+ }
+ return T;
+}
+
+/* Internal routines */
+
+
+/* MMDF mail abort stream
+ * Accepts: MAIL stream
+ */
+
+void mmdf_abort (MAILSTREAM *stream)
+{
+ if (LOCAL) { /* only if a file is open */
+ if (LOCAL->fd >= 0) close (LOCAL->fd);
+ if (LOCAL->ld >= 0) { /* have a mailbox lock? */
+ flock (LOCAL->ld,LOCK_UN);/* yes, release the lock */
+ close (LOCAL->ld); /* close the lock file */
+ unlink (LOCAL->lname); /* and delete it */
+ }
+ if (LOCAL->lname) fs_give ((void **) &LOCAL->lname);
+ /* free local text buffers */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data);
+ if (LOCAL->linebuf) fs_give ((void **) &LOCAL->linebuf);
+ if (LOCAL->line) fs_give ((void **) &LOCAL->line);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* MMDF open and lock mailbox
+ * Accepts: file name to open/lock
+ * file open mode
+ * destination buffer for lock file name
+ * type of locking operation (LOCK_SH or LOCK_EX)
+ */
+
+int mmdf_lock (char *file,int flags,int mode,DOTLOCK *lock,int op)
+{
+ int fd;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ (*bn) (BLOCK_FILELOCK,NIL);
+ /* try locking the easy way */
+ if (dotlock_lock (file,lock,-1)) {
+ /* got dotlock file, easy open */
+ if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
+ else dotlock_unlock (lock); /* open failed, free the dotlock */
+ }
+ /* no dot lock file, open file now */
+ else if ((fd = open (file,flags,mode)) >= 0) {
+ /* try paranoid way to make a dot lock file */
+ if (dotlock_lock (file,lock,fd)) {
+ close (fd); /* get fresh fd in case of timing race */
+ if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
+ /* open failed, free the dotlock */
+ else dotlock_unlock (lock);
+ }
+ else flock (fd,op); /* paranoid way failed, just flock() it */
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return fd;
+}
+
+/* MMDF unlock and close mailbox
+ * Accepts: file descriptor
+ * (optional) mailbox stream to check atime/mtime
+ * (optional) lock file name
+ */
+
+void mmdf_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock)
+{
+ if (stream) { /* need to muck with times? */
+ struct stat sbuf;
+ time_t tp[2];
+ time_t now = time (0);
+ fstat (fd,&sbuf); /* get file times */
+ if (LOCAL->ld >= 0) { /* yes, readwrite session? */
+ tp[0] = now; /* set atime to now */
+ /* set mtime to (now - 1) if necessary */
+ tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
+ }
+ else if (stream->recent) { /* readonly with recent messages */
+ if ((sbuf.st_atime >= sbuf.st_mtime) ||
+ (sbuf.st_atime >= sbuf.st_ctime))
+ /* keep past mtime, whack back atime */
+ tp[0] = (tp[1] = (sbuf.st_mtime < now) ? sbuf.st_mtime : now) - 1;
+ else now = 0; /* no time change needed */
+ }
+ /* readonly with no recent messages */
+ else if ((sbuf.st_atime < sbuf.st_mtime) ||
+ (sbuf.st_atime < sbuf.st_ctime)) {
+ tp[0] = now; /* set atime to now */
+ /* set mtime to (now - 1) if necessary */
+ tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
+ }
+ else now = 0; /* no time change needed */
+ /* set the times, note change */
+ if (now && !utime (stream->mailbox,tp)) LOCAL->filetime = tp[1];
+ }
+ flock (fd,LOCK_UN); /* release flock'ers */
+ if (!stream) close (fd); /* close the file if no stream */
+ dotlock_unlock (lock); /* flush the lock file if any */
+}
+
+/* MMDF mail parse and lock mailbox
+ * Accepts: MAIL stream
+ * space to write lock file name
+ * type of locking operation
+ * Returns: T if parse OK, critical & mailbox is locked shared; NIL if failure
+ */
+
+int mmdf_parse (MAILSTREAM *stream,DOTLOCK *lock,int op)
+{
+ int ti,zn,m;
+ unsigned long i,j,k;
+ unsigned char c,*s,*t,*u,tmp[MAILTMPLEN],date[30];
+ int retain = T;
+ unsigned long nmsgs = stream->nmsgs;
+ unsigned long prevuid = nmsgs ? mail_elt (stream,nmsgs)->private.uid : 0;
+ unsigned long recent = stream->recent;
+ unsigned long oldnmsgs = stream->nmsgs;
+ short silent = stream->silent;
+ short pseudoseen = NIL;
+ struct stat sbuf;
+ STRING bs;
+ FDDATA d;
+ MESSAGECACHE *elt;
+ mail_lock (stream); /* guard against recursion or pingers */
+ /* toss out previous descriptor */
+ if (LOCAL->fd >= 0) close (LOCAL->fd);
+ MM_CRITICAL (stream); /* open and lock mailbox (shared OK) */
+ if ((LOCAL->fd = mmdf_lock (stream->mailbox,(LOCAL->ld >= 0) ?
+ O_RDWR : O_RDONLY,
+ (long)mail_parameters(NIL,GET_MBXPROTECTION,NIL),
+ lock,op)) < 0) {
+ sprintf (tmp,"Mailbox open failed, aborted: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ mmdf_abort (stream);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream); /* done with critical */
+ return NIL;
+ }
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* validate change in size */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (tmp,"Mailbox shrank from %lu to %lu bytes, aborted",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR); /* this is pretty bad */
+ mmdf_unlock (LOCAL->fd,stream,lock);
+ mmdf_abort (stream);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream); /* done with critical */
+ return NIL;
+ }
+
+ /* new data? */
+ else if (i = sbuf.st_size - LOCAL->filesize) {
+ d.fd = LOCAL->fd; /* yes, set up file descriptor */
+ d.pos = LOCAL->filesize; /* get to that position in the file */
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* file chunk size */
+ INIT (&bs,fd_string,&d,i); /* initialize stringstruct */
+ /* skip leading whitespace for broken MTAs */
+ while (((c = CHR (&bs)) == '\n') || (c == '\r') ||
+ (c == ' ') || (c == '\t')) SNX (&bs);
+ if (SIZE (&bs)) { /* read new data */
+ /* remember internal header position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ s = mmdf_mbxline (stream,&bs,&i);
+ stream->silent = T; /* quell main program new message events */
+ do { /* read MMDF header */
+ if (!(i && ISMMDF (s))){/* see if valid MMDF header */
+ sprintf (tmp,"Unexpected changes to mailbox (try restarting): %.20s",
+ (char *) s);
+ /* see if we can back up to a line */
+ if (i && (j > MMDFHDRLEN)) {
+ SETPOS (&bs,j -= MMDFHDRLEN);
+ /* read previous line */
+ s = mmdf_mbxline (stream,&bs,&i);
+ /* kill the error if it looks good */
+ if (i && ISMMDF (s)) tmp[0] = '\0';
+ }
+ if (tmp[0]) {
+ MM_LOG (tmp,ERROR);
+ mmdf_unlock (LOCAL->fd,stream,lock);
+ mmdf_abort (stream);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream);
+ return NIL;
+ }
+ }
+ /* instantiate first new message */
+ mail_exists (stream,++nmsgs);
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ recent++; /* assume recent by default */
+ elt->recent = T;
+ /* note position/size of internal header */
+ elt->private.special.offset = j;
+ elt->private.special.text.size = i;
+
+ s = mmdf_mbxline (stream,&bs,&i);
+ ti = 0; /* assume not a valid date */
+ zn = 0,t = NIL;
+ if (i) VALID (s,t,ti,zn);
+ if (ti) { /* generate plausible IMAPish date string */
+ /* this is also part of header */
+ elt->private.special.text.size += i;
+ date[2] = date[6] = date[20] = '-'; date[11] = ' ';
+ date[14] = date[17] = ':';
+ /* dd */
+ date[0] = t[ti - 2]; date[1] = t[ti - 1];
+ /* mmm */
+ date[3] = t[ti - 6]; date[4] = t[ti - 5]; date[5] = t[ti - 4];
+ /* hh */
+ date[12] = t[ti + 1]; date[13] = t[ti + 2];
+ /* mm */
+ date[15] = t[ti + 4]; date[16] = t[ti + 5];
+ if (t[ti += 6]==':'){ /* ss */
+ date[18] = t[++ti]; date[19] = t[++ti];
+ ti++; /* move to space */
+ }
+ else date[18] = date[19] = '0';
+ /* yy -- advance over timezone if necessary */
+ if (zn == ti) ti += (((t[zn+1] == '+') || (t[zn+1] == '-')) ? 6 : 4);
+ date[7] = t[ti + 1]; date[8] = t[ti + 2];
+ date[9] = t[ti + 3]; date[10] = t[ti + 4];
+ /* zzz */
+ t = zn ? (t + zn + 1) : (unsigned char *) "LCL";
+ date[21] = *t++; date[22] = *t++; date[23] = *t++;
+ if ((date[21] != '+') && (date[21] != '-')) date[24] = '\0';
+ else { /* numeric time zone */
+ date[24] = *t++; date[25] = *t++;
+ date[26] = '\0'; date[20] = ' ';
+ }
+ /* set internal date */
+ if (!mail_parse_date (elt,date)) {
+ sprintf (tmp,"Unable to parse internal date: %s",(char *) date);
+ MM_LOG (tmp,WARN);
+ }
+ }
+ else { /* make date from file date */
+ struct tm *tm = gmtime (&sbuf.st_mtime);
+ elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
+ elt->year = tm->tm_year + 1900 - BASEYEAR;
+ elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
+ elt->seconds = tm->tm_sec;
+ elt->zhours = 0; elt->zminutes = 0;
+ t = NIL; /* suppress line read */
+ }
+ /* header starts here */
+ elt->private.msg.header.offset = elt->private.special.text.size;
+
+ do { /* look for message body */
+ j = GETPOS (&bs); /* note position before line */
+ if (t) s = t = mmdf_mbxline (stream,&bs,&i);
+ else t = s; /* this line read was suppressed */
+ if (ISMMDF (s)) { /* found terminator in header? */
+ SETPOS (&bs,j); /* oops, back up before line */
+ /* must insert a newline */
+ elt->private.spare.data++;
+ break; /* punt */
+ }
+ /* this line is part of header */
+ elt->private.msg.header.text.size += i;
+ if (i) switch (*s) { /* check header lines */
+ case 'X': /* possible X-???: line */
+ if (s[1] == '-') { /* must be immediately followed by hyphen */
+ /* X-Status: becomes Status: in S case */
+ if (s[2] == 'S' && s[3] == 't' && s[4] == 'a' && s[5] == 't' &&
+ s[6] == 'u' && s[7] == 's' && s[8] == ':') s += 2;
+ /* possible X-Keywords */
+ else if (s[2] == 'K' && s[3] == 'e' && s[4] == 'y' &&
+ s[5] == 'w' && s[6] == 'o' && s[7] == 'r' &&
+ s[8] == 'd' && s[9] == 's' && s[10] == ':') {
+ SIZEDTEXT uf;
+ retain = NIL; /* don't retain continuation */
+ s += 11; /* flush leading whitespace */
+ while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n'))){
+ while (*s == ' ') s++;
+ /* find end of keyword */
+ if (!(u = strpbrk (s," \n\r"))) u = s + strlen (s);
+ /* got a keyword? */
+ if ((k = (u - s)) && (k <= MAXUSERFLAG)) {
+ uf.data = (unsigned char *) s;
+ uf.size = k;
+ for (j = 0; (j < NUSERFLAGS) && stream->user_flags[j]; ++j)
+ if (!compare_csizedtext (stream->user_flags[j],&uf)) {
+ elt->user_flags |= ((long) 1) << j;
+ break;
+ }
+ }
+ s = u; /* advance to next keyword */
+ }
+ break;
+ }
+
+ /* possible X-IMAP */
+ else if ((s[2] == 'I') && (s[3] == 'M') && (s[4] == 'A') &&
+ (s[5] == 'P') && ((m = (s[6] == ':')) ||
+ ((s[6] == 'b') && (s[7] == 'a') &&
+ (s[8] == 's') && (s[9] == 'e') &&
+ (s[10] == ':')))) {
+ retain = NIL; /* don't retain continuation */
+ if ((nmsgs == 1) && !stream->uid_validity) {
+ /* advance to data */
+ s += m ? 7 : 11;
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ j = 0; /* slurp UID validity */
+ /* found a digit? */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ /* must have valid UID validity and UID last */
+ if (j && isdigit (*s)) {
+ /* pseudo-header seen if X-IMAP */
+ if (m) pseudoseen = LOCAL->pseudo = T;
+ /* save UID validity */
+ stream->uid_validity = j;
+ j = 0; /* slurp UID last */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* save UID last */
+ stream->uid_last = j;
+ /* process keywords */
+ for (j = 0; (*s != '\n') && ((*s != '\r')||(s[1] != '\n'));
+ s = u,j++) {
+ /* flush leading whitespace */
+ while (*s == ' ') s++;
+ u = strpbrk (s," \n\r");
+ /* got a keyword? */
+ if ((j < NUSERFLAGS) && (k = (u - s)) &&
+ (k <= MAXUSERFLAG)) {
+ if (stream->user_flags[j])
+ fs_give ((void **) &stream->user_flags[j]);
+ stream->user_flags[j] = (char *) fs_get (k + 1);
+ strncpy (stream->user_flags[j],s,k);
+ stream->user_flags[j][k] = '\0';
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ /* possible X-UID */
+ else if (s[2] == 'U' && s[3] == 'I' && s[4] == 'D' &&
+ s[5] == ':') {
+ retain = NIL; /* don't retain continuation */
+ /* only believe if have a UID validity */
+ if (stream->uid_validity && ((nmsgs > 1) || !pseudoseen)) {
+ s += 6; /* advance to UID value */
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ j = 0;
+ /* found a digit? */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* flush remainder of line */
+ while (*s != '\n') s++;
+ /* make sure not duplicated */
+ if (elt->private.uid)
+ sprintf (tmp,"Message %lu UID %lu already has UID %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,elt->private.uid);
+ /* make sure UID doesn't go backwards */
+ else if (j <= prevuid)
+ sprintf (tmp,"Message %lu UID %lu less than %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,prevuid + 1);
+#if 0 /* this is currently broken by UIDPLUS */
+ /* or skip by mailbox's recorded last */
+ else if (j > stream->uid_last)
+ sprintf (tmp,"Message %lu UID %lu greater than last %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,stream->uid_last);
+#endif
+ else { /* normal UID case */
+ prevuid = elt->private.uid = j;
+#if 1 /* temporary kludge for UIDPLUS */
+ if (prevuid > stream->uid_last) {
+ stream->uid_last = prevuid;
+ LOCAL->ddirty = LOCAL->dirty = T;
+ }
+#endif
+ break; /* exit this cruft */
+ }
+ MM_LOG (tmp,WARN);
+ /* invalidate UID validity */
+ stream->uid_validity = 0;
+ elt->private.uid = 0;
+ }
+ break;
+ }
+ }
+ /* otherwise fall into S case */
+
+ case 'S': /* possible Status: line */
+ if (s[0] == 'S' && s[1] == 't' && s[2] == 'a' && s[3] == 't' &&
+ s[4] == 'u' && s[5] == 's' && s[6] == ':') {
+ retain = NIL; /* don't retain continuation */
+ s += 6; /* advance to status flags */
+ do switch (*s++) {/* parse flags */
+ case 'R': /* message read */
+ elt->seen = T;
+ break;
+ case 'O': /* message old */
+ if (elt->recent) {
+ elt->recent = NIL;
+ recent--; /* it really wasn't recent */
+ }
+ break;
+ case 'D': /* message deleted */
+ elt->deleted = T;
+ break;
+ case 'F': /* message flagged */
+ elt->flagged = T;
+ break;
+ case 'A': /* message answered */
+ elt->answered = T;
+ break;
+ case 'T': /* message is a draft */
+ elt->draft = T;
+ break;
+ default: /* some other crap */
+ break;
+ } while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n')));
+ break; /* all done */
+ }
+ /* otherwise fall into default case */
+
+ default: /* ordinary header line */
+ if ((*s == 'S') || (*s == 's') ||
+ (((*s == 'X') || (*s == 'x')) && (s[1] == '-'))) {
+ unsigned char *e,*v;
+ /* must match what mail_filter() does */
+ for (u = s,v = tmp,e = u + min (i,MAILTMPLEN - 1);
+ (u < e) && ((c = (*u ? *u : (*u = ' '))) != ':') &&
+ ((c > ' ') || ((c != ' ') && (c != '\t') &&
+ (c != '\r') && (c != '\n')));
+ *v++ = *u++);
+ *v = '\0'; /* tie off */
+ /* matches internal header? */
+ if (!compare_cstring (tmp,"STATUS") ||
+ !compare_cstring (tmp,"X-STATUS") ||
+ !compare_cstring (tmp,"X-KEYWORDS") ||
+ !compare_cstring (tmp,"X-UID") ||
+ !compare_cstring (tmp,"X-IMAP") ||
+ !compare_cstring (tmp,"X-IMAPBASE")) {
+ char err[MAILTMPLEN];
+ sprintf (err,"Discarding bogus %s header in message %lu",
+ (char *) tmp,elt->msgno);
+ MM_LOG (err,WARN);
+ retain = NIL; /* don't retain continuation */
+ break; /* different case or something */
+ }
+ }
+ /* retain or non-continuation? */
+ if (retain || ((*s != ' ') && (*s != '\t'))) {
+ retain = T; /* retaining continuation now */
+ /* line length in LF format newline */
+ for (j = k = 0; j < i; ++j) if (s[j] != '\r') ++k;
+ /* "internal" header size */
+ elt->private.spare.data += k;
+ /* message size */
+ elt->rfc822_size += k + 1;
+ }
+ else {
+ char err[MAILTMPLEN];
+ sprintf (err,"Discarding bogus continuation in msg %lu: %.80s",
+ elt->msgno,(char *) s);
+ if (u = strpbrk (err,"\r\n")) *u = '\0';
+ MM_LOG (err,WARN);
+ break; /* different case or something */
+ }
+ break;
+ }
+ } while (i && (*t != '\n') && ((*t != '\r') || (t[1] != '\n')));
+ /* "internal" header sans trailing newline */
+ if (i) elt->private.spare.data--;
+ /* assign a UID if none found */
+ if (((nmsgs > 1) || !pseudoseen) && !elt->private.uid) {
+ prevuid = elt->private.uid = ++stream->uid_last;
+ elt->private.dirty = T;
+ }
+ else elt->private.dirty = elt->recent;
+
+ /* note size of header, location of text */
+ elt->private.msg.header.text.size =
+ (elt->private.msg.text.offset =
+ (LOCAL->filesize + GETPOS (&bs)) - elt->private.special.offset) -
+ elt->private.special.text.size;
+ /* note current position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ if (i) do { /* look for next message */
+ s = mmdf_mbxline (stream,&bs,&i);
+ if (i) { /* got new data? */
+ if (ISMMDF (s)) break;
+ else { /* not a header line, add it to message */
+ elt->rfc822_size += i;
+ for (j = 0; j < i; ++j) switch (s[j]) {
+ case '\r': /* squeeze out CRs */
+ elt->rfc822_size -= 1;
+ break;
+ case '\n': /* LF becomes CRLF */
+ elt->rfc822_size += 1;
+ break;
+ default:
+ break;
+ }
+ /* update current position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ }
+ }
+ } while (i); /* until found a header */
+ elt->private.msg.text.text.size = j -
+ (elt->private.special.offset + elt->private.msg.text.offset);
+ if (i) { /* get next header line */
+ /* remember first internal header position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ s = mmdf_mbxline (stream,&bs,&i);
+ }
+ /* until end of buffer */
+ } while (!stream->sniff && i);
+ if (pseudoseen) { /* flush pseudo-message if present */
+ /* decrement recent count */
+ if (mail_elt (stream,1)->recent) recent--;
+ /* and the exists count */
+ mail_exists (stream,nmsgs--);
+ mail_expunged(stream,1);/* fake an expunge of that message */
+ }
+ /* need to start a new UID validity? */
+ if (!stream->uid_validity) {
+ stream->uid_validity = time (0);
+ /* in case a whiner with no life */
+ if (mail_parameters (NIL,GET_USERHASNOLIFE,NIL))
+ stream->uid_nosticky = T;
+ else if (nmsgs) { /* don't bother if empty file */
+ /* make dirty to restart UID epoch */
+ LOCAL->ddirty = LOCAL->dirty = T;
+ /* need to rewrite msg 1 if not pseudo */
+ if (!LOCAL->pseudo) mail_elt (stream,1)->private.dirty = T;
+ MM_LOG ("Assigning new unique identifiers to all messages",NIL);
+ }
+ }
+ stream->nmsgs = oldnmsgs; /* whack it back down */
+ stream->silent = silent; /* restore old silent setting */
+ /* notify upper level of new mailbox sizes */
+ mail_exists (stream,nmsgs);
+ mail_recent (stream,recent);
+ /* mark dirty so O flags are set */
+ if (recent) LOCAL->dirty = T;
+ }
+ }
+ /* no change, don't babble if never got time */
+ else if (LOCAL->filetime && LOCAL->filetime != sbuf.st_mtime)
+ MM_LOG ("New mailbox modification time but apparently no changes",WARN);
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ LOCAL->filetime = sbuf.st_mtime;
+ return T; /* return the winnage */
+}
+
+/* MMDF read line from mailbox
+ * Accepts: mail stream
+ * stringstruct
+ * pointer to line size
+ * Returns: pointer to input line
+ */
+
+char *mmdf_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size)
+{
+ unsigned long i,j,k,m;
+ char *s,*t,*te;
+ char *ret = "";
+ /* flush old buffer */
+ if (LOCAL->line) fs_give ((void **) &LOCAL->line);
+ /* if buffer needs refreshing */
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ if (SIZE (bs)) { /* find newline */
+ /* end of fast scan */
+ te = (t = (s = bs->curpos) + bs->cursize) - 12;
+ while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
+ --s; /* back up */
+ break; /* exit loop */
+ }
+ /* final character-at-a-time scan */
+ while ((s < t) && (*s != '\n')) ++s;
+ /* difficult case if line spans buffer */
+ if ((i = s - bs->curpos) == bs->cursize) {
+ /* have space in line buffer? */
+ if (i > LOCAL->linebuflen) {
+ fs_give ((void **) &LOCAL->linebuf);
+ LOCAL->linebuf = (char *) fs_get (LOCAL->linebuflen = i);
+ }
+ /* remember what we have so far */
+ memcpy (LOCAL->linebuf,bs->curpos,i);
+ /* load next buffer */
+ SETPOS (bs,k = GETPOS (bs) + i);
+ /* end of fast scan */
+ te = (t = (s = bs->curpos) + bs->cursize) - 12;
+ /* fast scan in overlap buffer */
+ while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
+ --s; /* back up */
+ break; /* exit loop */
+ }
+
+ /* final character-at-a-time scan */
+ while ((s < t) && (*s != '\n')) ++s;
+ /* huge line? */
+ if ((j = s - bs->curpos) == bs->cursize) {
+ SETPOS (bs,GETPOS (bs) + j);
+ /* look for end of line (s-l-o-w!!) */
+ for (m = SIZE (bs); m && (SNX (bs) != '\n'); --m,++j);
+ SETPOS (bs,k); /* go back to where it started */
+ }
+ /* got size of data, make buffer for return */
+ ret = LOCAL->line = (char *) fs_get (i + j + 2);
+ /* copy first chunk */
+ memcpy (ret,LOCAL->linebuf,i);
+ while (j) { /* copy remainder */
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ memcpy (ret + i,bs->curpos,k = min (j,bs->cursize));
+ i += k; /* account for this much read in */
+ j -= k;
+ bs->curpos += k; /* increment new position */
+ bs->cursize -= k; /* eat that many bytes */
+ }
+ /* read newline at end */
+ if (SIZE (bs)) ret[i++] = SNX (bs);
+ ret[i] = '\0'; /* makes debugging easier */
+ }
+ else { /* this is easy */
+ ret = bs->curpos; /* string it at this position */
+ bs->curpos += ++i; /* increment new position */
+ bs->cursize -= i; /* eat that many bytes */
+ }
+ *size = i; /* return that to user */
+ }
+ else *size = 0; /* end of data, return empty */
+ /* embedded MMDF header at end of line? */
+ if ((*size > sizeof (MMDFHDRTXT)) &&
+ (s = ret + *size - (i = sizeof (MMDFHDRTXT) - 1)) && ISMMDF (s)) {
+ SETPOS (bs,GETPOS (bs) - i);/* back up to start of MMDF header */
+ *size -= i; /* reduce length of line */
+ ret[*size - 1] = '\n'; /* force newline at end */
+ }
+ return ret;
+}
+
+/* MMDF make pseudo-header
+ * Accepts: MAIL stream
+ * buffer to write pseudo-header
+ * Returns: length of pseudo-header
+ */
+
+unsigned long mmdf_pseudo (MAILSTREAM *stream,char *hdr)
+{
+ int i;
+ char *s,tmp[MAILTMPLEN];
+ time_t now = time (0);
+ rfc822_fixed_date (tmp);
+ sprintf (hdr,"%sFrom %s %.24s\nDate: %s\nFrom: %s <%s@%.80s>\nSubject: %s\nMessage-ID: <%lu@%.80s>\nX-IMAP: %010lu %010lu",
+ mmdfhdr,pseudo_from,ctime (&now),
+ tmp,pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
+ (unsigned long) now,mylocalhost (),stream->uid_validity,
+ stream->uid_last);
+ for (s = hdr + strlen (hdr),i = 0; i < NUSERFLAGS; ++i)
+ if (stream->user_flags[i])
+ sprintf (s += strlen (s)," %s",stream->user_flags[i]);
+ sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n%s",pseudo_msg,mmdfhdr);
+ return strlen (hdr);
+}
+
+/* MMDF make status string
+ * Accepts: MAIL stream
+ * destination string to write
+ * message cache entry
+ * UID to write if non-zero (else use elt->private.uid)
+ * non-zero flag to write UID (.LT. 0 to write UID base info too)
+ * Returns: length of string
+ */
+
+unsigned long mmdf_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
+ unsigned long uid,long flag)
+{
+ char *t,stack[64];
+ char *s = status;
+ unsigned long n;
+ int pad = 50;
+ int sticky = uid ? T : !stream->uid_nosticky;
+ /* This used to use sprintf(), but thanks to certain cretinous C libraries
+ with horribly slow implementations of sprintf() I had to change it to this
+ mess. At least it should be fast. */
+ if ((flag < 0) && sticky) { /* need to write X-IMAPbase: header? */
+ *s++ = 'X'; *s++ = '-'; *s++ = 'I'; *s++ = 'M'; *s++ = 'A'; *s++ = 'P';
+ *s++ = 'b'; *s++ = 'a'; *s++ = 's'; *s++ = 'e'; *s++ = ':'; *s++ = ' ';
+ t = stack;
+ n = stream->uid_validity; /* push UID validity digits on the stack */
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ /* pop UID validity digits from stack */
+ while (t > stack) *s++ = *--t;
+ *s++ = ' ';
+ n = stream->uid_last; /* push UID last digits on the stack */
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ /* pop UID last digits from stack */
+ while (t > stack) *s++ = *--t;
+ for (n = 0; n < NUSERFLAGS; ++n) if (t = stream->user_flags[n])
+ for (*s++ = ' '; *t; *s++ = *t++);
+ *s++ = '\n';
+ pad += 30; /* increased padding if have IMAPbase */
+ }
+ *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't'; *s++ = 'u'; *s++ = 's';
+ *s++ = ':'; *s++ = ' ';
+ if (elt->seen) *s++ = 'R';
+ /* only write O if have a UID */
+ if (flag && (!elt->recent || !LOCAL->appending)) *s++ = 'O';
+ *s++ = '\n';
+ *s++ = 'X'; *s++ = '-'; *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't';
+ *s++ = 'u'; *s++ = 's'; *s++ = ':'; *s++ = ' ';
+ if (elt->deleted) *s++ = 'D';
+ if (elt->flagged) *s++ = 'F';
+ if (elt->answered) *s++ = 'A';
+ if (elt->draft) *s++ = 'T';
+ *s++ = '\n';
+
+ if (sticky) { /* only do this if UIDs sticky */
+ *s++ = 'X'; *s++ = '-'; *s++ = 'K'; *s++ = 'e'; *s++ = 'y'; *s++ = 'w';
+ *s++ = 'o'; *s++ = 'r'; *s++ = 'd'; *s++ = 's'; *s++ = ':';
+ if (n = elt->user_flags) do {
+ *s++ = ' ';
+ for (t = stream->user_flags[find_rightmost_bit (&n)]; *t; *s++ = *t++);
+ } while (n);
+ n = s - status; /* get size of stuff so far */
+ /* pad X-Keywords to make size constant */
+ if (n < pad) for (n = pad - n; n > 0; --n) *s++ = ' ';
+ *s++ = '\n';
+ if (flag) { /* want to include UID? */
+ t = stack;
+ /* push UID digits on the stack */
+ n = uid ? uid : elt->private.uid;
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ *s++ = 'X'; *s++ = '-'; *s++ = 'U'; *s++ = 'I'; *s++ = 'D'; *s++ = ':';
+ *s++ = ' ';
+ /* pop UID from stack */
+ while (t > stack) *s++ = *--t;
+ *s++ = '\n';
+ }
+ }
+ *s++ = '\n'; *s = '\0'; /* end of extended message status */
+ return s - status; /* return size of resulting string */
+}
+
+/* Rewrite mailbox file
+ * Accepts: MAIL stream, must be critical and locked
+ * return pointer to number of expunged messages if want expunge
+ * lock file name
+ * expunge sequence, not deleted flag
+ * Returns: T if success and mailbox unlocked, NIL if failure
+ */
+
+#define OVERFLOWBUFLEN 8192 /* initial overflow buffer length */
+
+long mmdf_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock,
+ long flags)
+{
+ MESSAGECACHE *elt;
+ MMDFFILE f;
+ char *s;
+ time_t tp[2];
+ long ret,flag;
+ unsigned long i,j;
+ unsigned long recent = stream->recent;
+ unsigned long size = LOCAL->pseudo ? mmdf_pseudo (stream,LOCAL->buf) : 0;
+ if (nexp) *nexp = 0; /* initially nothing expunged */
+ /* calculate size of mailbox after rewrite */
+ for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs; i++) {
+ elt = mail_elt (stream,i); /* get cache */
+ if (!(nexp && elt->deleted && (flags ? elt->sequence : T))) {
+ /* add RFC822 size of this message */
+ size += elt->private.special.text.size + elt->private.spare.data +
+ mmdf_xstatus (stream,LOCAL->buf,elt,NIL,flag) +
+ elt->private.msg.text.text.size + MMDFHDRLEN;
+ flag = 1; /* only count X-IMAPbase once */
+ }
+ }
+ /* no messages, has a life, and no pseudo */
+ if (!size && !mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) {
+ LOCAL->pseudo = T; /* so make a pseudo-message now */
+ size = mmdf_pseudo (stream,LOCAL->buf);
+ }
+ /* extend the file as necessary */
+ if (ret = mmdf_extend (stream,size)) {
+ /* Set up buffered I/O file structure
+ * curpos current position being written through buffering
+ * filepos current position being written physically to the disk
+ * bufpos current position being written in the buffer
+ * protect current maximum position that can be written to the disk
+ * before buffering is forced
+ * The code tries to buffer so that that disk is written in multiples of
+ * OVERBLOWBUFLEN bytes.
+ */
+ f.stream = stream; /* note mail stream */
+ f.curpos = f.filepos = 0; /* start of file */
+ f.protect = stream->nmsgs ? /* initial protection pointer */
+ mail_elt (stream,1)->private.special.offset : 8192;
+ f.bufpos = f.buf = (char *) fs_get (f.buflen = OVERFLOWBUFLEN);
+
+ if (LOCAL->pseudo) /* update pseudo-header */
+ mmdf_write (&f,LOCAL->buf,mmdf_pseudo (stream,LOCAL->buf));
+ /* loop through all messages */
+ for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs;) {
+ elt = mail_elt (stream,i);/* get cache */
+ /* expunge this message? */
+ if (nexp && elt->deleted && (flags ? elt->sequence : T)) {
+ /* one less recent message */
+ if (elt->recent) --recent;
+ mail_expunged(stream,i);/* notify upper levels */
+ ++*nexp; /* count up one more expunged message */
+ }
+ else { /* preserve this message */
+ i++; /* advance to next message */
+ if ((flag < 0) || /* need to rewrite message? */
+ elt->private.dirty || (f.curpos != elt->private.special.offset) ||
+ (elt->private.msg.header.text.size !=
+ (elt->private.spare.data +
+ mmdf_xstatus (stream,LOCAL->buf,elt,NIL,flag)))) {
+ unsigned long newoffset = f.curpos;
+ /* yes, seek to internal header */
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
+ /* see if need to squeeze out a CR */
+ if (LOCAL->buf[elt->private.special.text.size - 2] == '\r') {
+ LOCAL->buf[--elt->private.special.text.size - 1] = '\n';
+ --size; /* squeezed out a CR from PC */
+ }
+ /* protection pointer moves to RFC822 header */
+ f.protect = elt->private.special.offset +
+ elt->private.msg.header.offset;
+ /* write internal header */
+ mmdf_write (&f,LOCAL->buf,elt->private.special.text.size);
+ /* get RFC822 header */
+ s = mmdf_header (stream,elt->msgno,&j,FT_INTERNAL);
+ /* in case this got decremented */
+ elt->private.msg.header.offset = elt->private.special.text.size;
+ /* header size, sans trailing newline */
+ if ((j < 2) || (s[j - 2] == '\n')) j--;
+ /* this can happen if CRs were squeezed */
+ if (j < elt->private.spare.data) {
+ /* so fix up counts */
+ size -= elt->private.spare.data - j;
+ elt->private.spare.data = j;
+ }
+ else if (j != elt->private.spare.data)
+ fatal ("header size inconsistent");
+ /* protection pointer moves to RFC822 text */
+ f.protect = elt->private.special.offset +
+ elt->private.msg.text.offset;
+ mmdf_write (&f,s,j); /* write RFC822 header */
+ /* write status and UID */
+ mmdf_write (&f,LOCAL->buf,
+ j = mmdf_xstatus (stream,LOCAL->buf,elt,NIL,flag));
+ flag = 1; /* only write X-IMAPbase once */
+ /* new file header size */
+ elt->private.msg.header.text.size = elt->private.spare.data + j;
+
+ /* did text move? */
+ if (f.curpos != f.protect) {
+ /* get message text */
+ s = mmdf_text_work (stream,elt,&j,FT_INTERNAL);
+ /* this can happen if CRs were squeezed */
+ if (j < elt->private.msg.text.text.size) {
+ /* so fix up counts */
+ size -= elt->private.msg.text.text.size - j;
+ elt->private.msg.text.text.size = j;
+ }
+ /* can't happen it says here */
+ else if (j > elt->private.msg.text.text.size)
+ fatal ("text size inconsistent");
+ /* new text offset, status/UID may change it */
+ elt->private.msg.text.offset = f.curpos - newoffset;
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset :
+ (f.curpos + j + MMDFHDRLEN);
+ mmdf_write (&f,s,j);/* write text */
+ /* write trailing newline */
+ mmdf_write (&f,mmdfhdr,MMDFHDRLEN);
+ }
+ else { /* tie off header and status */
+ mmdf_write (&f,NIL,NIL);
+ f.curpos = f.protect =/* restart everything at end of message */
+ f.filepos += elt->private.msg.text.text.size + MMDFHDRLEN;
+ }
+ /* new internal header offset */
+ elt->private.special.offset = newoffset;
+ elt->private.dirty =NIL;/* message is now clean */
+ }
+ else { /* no need to rewrite this message */
+ /* tie off previous message if needed */
+ mmdf_write (&f,NIL,NIL);
+ f.curpos = f.protect =/* restart everything at end of message */
+ f.filepos += elt->private.special.text.size +
+ elt->private.msg.header.text.size +
+ elt->private.msg.text.text.size + MMDFHDRLEN;
+ }
+ }
+ }
+
+ mmdf_write (&f,NIL,NIL); /* tie off final message */
+ if (size != f.filepos) fatal ("file size inconsistent");
+ fs_give ((void **) &f.buf); /* free buffer */
+ /* make sure tied off */
+ ftruncate (LOCAL->fd,LOCAL->filesize = size);
+ fsync (LOCAL->fd); /* make sure the updates take */
+ if (size && (flag < 0)) fatal ("lost UID base information");
+ /* no longer dirty */
+ LOCAL->ddirty = LOCAL->dirty = NIL;
+ /* notify upper level of new mailbox sizes */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ /* set atime to now, mtime a second earlier */
+ tp[1] = (tp[0] = time (0)) - 1;
+ /* set the times, note change */
+ if (!utime (stream->mailbox,tp)) LOCAL->filetime = tp[1];
+ close (LOCAL->fd); /* close and reopen file */
+ if ((LOCAL->fd = open (stream->mailbox,O_RDWR,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL)))
+ < 0) {
+ sprintf (LOCAL->buf,"Mailbox open failed, aborted: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ mmdf_abort (stream);
+ }
+ dotlock_unlock (lock); /* flush the lock file */
+ }
+ return ret; /* return state from algorithm */
+}
+
+/* Extend MMDF mailbox file
+ * Accepts: MAIL stream
+ * new desired size
+ * Return: T if success, else NIL
+ */
+
+long mmdf_extend (MAILSTREAM *stream,unsigned long size)
+{
+ unsigned long i = (size > LOCAL->filesize) ? size - LOCAL->filesize : 0;
+ if (i) { /* does the mailbox need to grow? */
+ if (i > LOCAL->buflen) { /* make sure have enough space */
+ /* this user won the lottery all right */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1);
+ }
+ memset (LOCAL->buf,'\0',i); /* get a block of nulls */
+ while (T) { /* until write successful or punt */
+ lseek (LOCAL->fd,LOCAL->filesize,L_SET);
+ if ((write (LOCAL->fd,LOCAL->buf,i) >= 0) && !fsync (LOCAL->fd)) break;
+ else {
+ long e = errno; /* note error before doing ftruncate */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ if (MM_DISKERROR (stream,e,NIL)) {
+ fsync (LOCAL->fd); /* user chose to punt */
+ sprintf (LOCAL->buf,"Unable to extend mailbox: %s",strerror (e));
+ if (!stream->silent) MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ }
+ }
+ }
+ return LONGT;
+}
+
+/* Write data to buffered file
+ * Accepts: buffered file pointer
+ * file data or NIL to indicate "flush buffer"
+ * date size (ignored for "flush buffer")
+ * Does not return until success
+ */
+
+void mmdf_write (MMDFFILE *f,char *buf,unsigned long size)
+{
+ unsigned long i,j,k;
+ if (buf) { /* doing buffered write? */
+ i = f->bufpos - f->buf; /* yes, get size of current buffer data */
+ /* yes, have space in current buffer chunk? */
+ if (j = i ? ((f->buflen - i) % OVERFLOWBUFLEN) : f->buflen) {
+ /* yes, fill up buffer as much as we can */
+ memcpy (f->bufpos,buf,k = min (j,size));
+ f->bufpos += k; /* new buffer position */
+ f->curpos += k; /* new current position */
+ if (j -= k) return; /* all done if still have buffer free space */
+ buf += k; /* full, get new unwritten data pointer */
+ size -= k; /* new data size */
+ i += k; /* new buffer data size */
+ }
+ /* This chunk of the buffer is full. See if can make some space by
+ * writing to the disk, if there's enough unprotected space to do so.
+ * Try to fill out any unaligned chunk, along with any subsequent full
+ * chunks that will fit in unprotected space.
+ */
+ /* any unprotected space we can write to? */
+ if (j = min (i,f->protect - f->filepos)) {
+ /* yes, filepos not at chunk boundary? */
+ if ((k = f->filepos % OVERFLOWBUFLEN) && ((k = OVERFLOWBUFLEN - k) < j))
+ j -= k; /* yes, and can write out partial chunk */
+ else k = 0; /* no partial chunk to write */
+ /* if at least a chunk free, write that too */
+ if (j > OVERFLOWBUFLEN) k += j - (j % OVERFLOWBUFLEN);
+ if (k) { /* write data if there is anything we can */
+ mmdf_phys_write (f,f->buf,k);
+ /* slide buffer */
+ if (i -= k) memmove (f->buf,f->buf + k,i);
+ f->bufpos = f->buf + i; /* new end of buffer */
+ }
+ }
+
+ /* Have flushed the buffer as best as possible. All done if no more
+ * data to write. Otherwise, if the buffer is empty AND if the unwritten
+ * data is larger than a chunk AND the unprotected space is also larger
+ * than a chunk, then write as many chunks as we can directly from the
+ * data. Buffer the rest, expanding the buffer as needed.
+ */
+ if (size) { /* have more data that we need to buffer? */
+ /* can write any of it to disk instead? */
+ if ((f->bufpos == f->buf) &&
+ ((j = min (f->protect - f->filepos,size)) > OVERFLOWBUFLEN)) {
+ /* write as much as we can right now */
+ mmdf_phys_write (f,buf,j -= (j % OVERFLOWBUFLEN));
+ buf += j; /* new data pointer */
+ size -= j; /* new data size */
+ f->curpos += j; /* advance current pointer */
+ }
+ if (size) { /* still have data that we need to buffer? */
+ /* yes, need to expand the buffer? */
+ if ((i = ((f->bufpos + size) - f->buf)) > f->buflen) {
+ /* note current position in buffer */
+ j = f->bufpos - f->buf;
+ i += OVERFLOWBUFLEN; /* yes, grow another chunk */
+ fs_resize ((void **) &f->buf,f->buflen = i - (i % OVERFLOWBUFLEN));
+ /* in case buffer relocated */
+ f->bufpos = f->buf + j;
+ }
+ /* buffer remaining data */
+ memcpy (f->bufpos,buf,size);
+ f->bufpos += size; /* new end of buffer */
+ f->curpos += size; /* advance current pointer */
+ }
+ }
+ }
+ else { /* flush buffer to disk */
+ mmdf_phys_write (f,f->buf,i = f->bufpos - f->buf);
+ f->bufpos = f->buf; /* reset buffer */
+ /* update positions */
+ f->curpos = f->protect = f->filepos;
+ }
+}
+
+/* Physical disk write
+ * Accepts: buffered file pointer
+ * buffer address
+ * buffer size
+ * Does not return until success
+ */
+
+void mmdf_phys_write (MMDFFILE *f,char *buf,size_t size)
+{
+ MAILSTREAM *stream = f->stream;
+ /* write data at desired position */
+ while (size && ((lseek (LOCAL->fd,f->filepos,L_SET) < 0) ||
+ (write (LOCAL->fd,buf,size) < 0))) {
+ int e;
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Unable to write to mailbox: %s",strerror (e = errno));
+ MM_LOG (tmp,ERROR);
+ MM_DISKERROR (NIL,e,T); /* serious problem, must retry */
+ }
+ f->filepos += size; /* update file position */
+}
diff --git a/imap/src/osdep/unix/mtx.c b/imap/src/osdep/unix/mtx.c
new file mode 100644
index 00000000..8e6f76e8
--- /dev/null
+++ b/imap/src/osdep/unix/mtx.c
@@ -0,0 +1,1371 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MTX mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 22 May 1990
+ * Last Edited: 11 October 2007
+ */
+
+
+/* FILE TIME SEMANTICS
+ *
+ * The atime is the last read time of the file.
+ * The mtime is the last flags update time of the file.
+ * The ctime is the last write time of the file.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+/* MTX I/O stream local data */
+
+typedef struct mtx_local {
+ unsigned int shouldcheck: 1; /* if ping should do a check instead */
+ unsigned int mustcheck: 1; /* if ping must do a check instead */
+ int fd; /* file descriptor for I/O */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* last snarf time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+} MTXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MTXLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *mtx_valid (char *name);
+int mtx_isvalid (char *name,char *tmp);
+void *mtx_parameters (long function,void *value);
+void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void mtx_list (MAILSTREAM *stream,char *ref,char *pat);
+void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mtx_create (MAILSTREAM *stream,char *mailbox);
+long mtx_delete (MAILSTREAM *stream,char *mailbox);
+long mtx_rename (MAILSTREAM *stream,char *old,char *newname);
+long mtx_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *mtx_open (MAILSTREAM *stream);
+void mtx_close (MAILSTREAM *stream,long options);
+void mtx_flags (MAILSTREAM *stream,char *sequence,long flags);
+char *mtx_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void mtx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long mtx_ping (MAILSTREAM *stream);
+void mtx_check (MAILSTREAM *stream);
+void mtx_snarf (MAILSTREAM *stream);
+long mtx_expunge (MAILSTREAM *stream,char *sequence,long options);
+long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+char *mtx_file (char *dst,char *name);
+long mtx_parse (MAILSTREAM *stream);
+MESSAGECACHE *mtx_elt (MAILSTREAM *stream,unsigned long msgno);
+void mtx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
+void mtx_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag);
+unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size);
+
+/* MTX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mtxdriver = {
+ "mtx", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_CRLF|DR_NOSTICKY|DR_LOCKING,
+ (DRIVER *) NIL, /* next driver */
+ mtx_valid, /* mailbox is valid for us */
+ mtx_parameters, /* manipulate parameters */
+ mtx_scan, /* scan mailboxes */
+ mtx_list, /* list mailboxes */
+ mtx_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ mtx_delete, /* delete mailbox */
+ mtx_rename, /* rename mailbox */
+ mtx_status, /* status of mailbox */
+ mtx_open, /* open mailbox */
+ mtx_close, /* close mailbox */
+ mtx_flags, /* fetch message "fast" attributes */
+ mtx_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mtx_header, /* fetch message header */
+ mtx_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ mtx_flag, /* modify flags */
+ mtx_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mtx_ping, /* ping mailbox to see if still alive */
+ mtx_check, /* check for new messages */
+ mtx_expunge, /* expunge deleted messages */
+ mtx_copy, /* copy messages to another mailbox */
+ mtx_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mtxproto = {&mtxdriver};
+
+/* MTX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mtx_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return mtx_isvalid (name,tmp) ? &mtxdriver : NIL;
+}
+
+
+/* MTX mail test for valid mailbox
+ * Accepts: mailbox name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int mtx_isvalid (char *name,char *tmp)
+{
+ int fd;
+ int ret = NIL;
+ char *s,file[MAILTMPLEN];
+ struct stat sbuf;
+ time_t tp[2];
+ errno = EINVAL; /* assume invalid argument */
+ /* if file, get its status */
+ if ((s = mtx_file (file,name)) && !stat (s,&sbuf)) {
+ if (!sbuf.st_size) { /* allow empty file if INBOX */
+ if ((s = mailboxfile (tmp,name)) && !*s) ret = T;
+ else errno = 0; /* empty file */
+ }
+ else if ((fd = open (file,O_RDONLY,NIL)) >= 0) {
+ memset (tmp,'\0',MAILTMPLEN);
+ if ((read (fd,tmp,64) >= 0) && (s = strchr (tmp,'\015')) &&
+ (s[1] == '\012')) { /* valid format? */
+ *s = '\0'; /* tie off header */
+ /* must begin with dd-mmm-yy" */
+ ret = (((tmp[2] == '-' && tmp[6] == '-') ||
+ (tmp[1] == '-' && tmp[5] == '-')) &&
+ (s = strchr (tmp+18,',')) && strchr (s+2,';')) ? T : NIL;
+ }
+ else errno = -1; /* bogus format */
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if (sbuf.st_ctime > sbuf.st_atime) {
+ tp[0] = sbuf.st_atime; /* preserve atime and mtime */
+ tp[1] = sbuf.st_mtime;
+ utime (file,tp); /* set the times */
+ }
+ }
+ }
+ /* in case INBOX but not mtx format */
+ else if ((errno == ENOENT) && !compare_cstring (name,"INBOX")) errno = -1;
+ return ret; /* return what we should */
+}
+
+/* MTX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mtx_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = mtx_file ((char *) value,"INBOX");
+ break;
+ }
+ return ret;
+}
+
+
+/* MTX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MTX mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mtx_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MTX mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MTX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return mtx_rename (stream,mailbox,NIL);
+}
+
+
+/* MTX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = T;
+ char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ if (!mtx_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'/')) && !s[1])))) {
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else if ((fd = open (file,O_RDWR,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get exclusive parse/append permission */
+ if ((ld = lockfd (fd,lock,LOCK_EX)) < 0) {
+ MM_LOG ("Unable to lock rename mailbox",ERROR);
+ return NIL;
+ }
+ /* lock out other users */
+ if (flock (fd,LOCK_EX|LOCK_NB)) {
+ close (fd); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ MM_LOG (tmp,ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return NIL;
+ }
+
+ if (newname) { /* want rename? */
+ if (s = strrchr (tmp,'/')) {/* found superior to destination name? */
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp,get_dir_protection (newname)))
+ ret = NIL;
+ else *s = c; /* restore full name */
+ }
+ /* rename the file */
+ if (ret && rename (file,tmp)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ else if (unlink (file)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* recreate file if renamed INBOX */
+ if (ret && !compare_cstring (old,"INBOX")) dummy_create (NIL,"INBOX.MTX");
+ return ret; /* return success */
+}
+
+/* Mtx Mail status
+ * Accepts: mail stream
+ * mailbox name
+ * status flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mtx_status (MAILSTREAM *stream,char *mbx,long flags)
+{
+ MAILSTATUS status;
+ unsigned long i;
+ MAILSTREAM *tstream = NIL;
+ MAILSTREAM *systream = NIL;
+ /* make temporary stream (unless this mbx) */
+ if (!stream && !(stream = tstream =
+ mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
+ status.flags = flags; /* return status values */
+ status.messages = stream->nmsgs;
+ status.recent = stream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
+ if (!mail_elt (stream,i)->seen) status.unseen++;
+ status.uidnext = stream->uid_last + 1;
+ status.uidvalidity = stream->uid_validity;
+ /* calculate post-snarf results */
+ if (!status.recent && stream->inbox &&
+ (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) {
+ status.messages += systream->nmsgs;
+ status.recent += systream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1; i <= systream->nmsgs; i++)
+ if (!mail_elt (systream,i)->seen) status.unseen++;
+ /* kludge but probably good enough */
+ status.uidnext += systream->nmsgs;
+ }
+ MM_STATUS(stream,mbx,&status);/* pass status to main program */
+ if (tstream) mail_close (tstream);
+ if (systream) mail_close (systream);
+ return T; /* success */
+}
+
+/* MTX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mtx_open (MAILSTREAM *stream)
+{
+ int fd,ld;
+ char tmp[MAILTMPLEN];
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&mtxproto);
+ if (stream->local) fatal ("mtx recycle stream");
+ user_flags (stream); /* set up user flags */
+ /* canonicalize the mailbox name */
+ if (!mtx_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ MM_LOG (tmp,ERROR);
+ }
+ if (stream->rdonly ||
+ (fd = open (tmp,O_RDWR,NIL)) < 0) {
+ if ((fd = open (tmp,O_RDONLY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else if (!stream->rdonly) { /* got it, but readonly */
+ MM_LOG ("Can't get write access to mailbox, access is readonly",WARN);
+ stream->rdonly = T;
+ }
+ }
+ stream->local = fs_get (sizeof (MTXLOCAL));
+ LOCAL->fd = fd; /* bind the file */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ /* get shared parse permission */
+ if ((ld = lockfd (fd,tmp,LOCK_SH)) < 0) {
+ MM_LOG ("Unable to lock open mailbox",ERROR);
+ return NIL;
+ }
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH); /* lock the file */
+ (*bn) (BLOCK_NONE,NIL);
+ unlockfd (ld,tmp); /* release shared parse permission */
+ LOCAL->filesize = 0; /* initialize parsed file size */
+ /* time not set up yet */
+ LOCAL->lastsnarf = LOCAL->filetime = 0;
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (mtx_ping (stream) && !stream->nmsgs)
+ MM_LOG ("Mailbox is empty",(long) NIL);
+ if (!LOCAL) return NIL; /* failure if stream died */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft =
+ stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ return stream; /* return stream to caller */
+}
+
+/* MTX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mtx_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ if (options & CL_EXPUNGE) mtx_expunge (stream,NIL,NIL);
+ stream->silent = silent; /* restore previous status */
+ flock (LOCAL->fd,LOCK_UN); /* unlock local file */
+ close (LOCAL->fd); /* close the local file */
+ /* free local text buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+
+/* MTX mail fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ * Sniffs at file to see if some other process changed the flags
+ */
+
+void mtx_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ unsigned long i;
+ if (mtx_ping (stream) && /* ping mailbox, get new status for messages */
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if (mail_elt (stream,i)->sequence) mtx_elt (stream,i);
+}
+
+/* MTX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mtx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get to header position */
+ lseek (LOCAL->fd,mtx_hdrpos (stream,msgno,length),L_SET);
+ /* is buffer big enough? */
+ if (*length > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = *length) + 1);
+ }
+ LOCAL->buf[*length] = '\0'; /* tie off string */
+ /* slurp the data */
+ read (LOCAL->fd,LOCAL->buf,*length);
+ return (char *) LOCAL->buf;
+}
+
+/* MTX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: T, always
+ */
+
+long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ FDDATA d;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mtx_elt (stream,msgno); /* get message status */
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ elt->seen = T; /* mark message as seen */
+ /* recalculate status */
+ mtx_update_status (stream,msgno,NIL);
+ MM_FLAGS (stream,msgno);
+ }
+ /* find header position */
+ i = mtx_hdrpos (stream,msgno,&j);
+ d.fd = LOCAL->fd; /* set up file descriptor */
+ d.pos = i + j;
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE;
+ INIT (bs,fd_string,&d,elt->rfc822_size - j);
+ return T; /* success */
+}
+
+/* MTX mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ */
+
+void mtx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ time_t tp[2];
+ struct stat sbuf;
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* make sure read comes after all that */
+ utime (stream->mailbox,tp);
+ }
+}
+
+
+/* MTX mail per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ struct stat sbuf;
+ /* maybe need to do a checkpoint? */
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ LOCAL->filetime = 0; /* don't do this test for any other messages */
+ }
+ /* recalculate status */
+ mtx_update_status (stream,elt->msgno,NIL);
+}
+
+/* MTX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long mtx_ping (MAILSTREAM *stream)
+{
+ unsigned long i = 1;
+ long r = T;
+ int ld;
+ char lock[MAILTMPLEN];
+ struct stat sbuf;
+ if (stream && LOCAL) { /* only if stream already open */
+ fstat (LOCAL->fd,&sbuf); /* get current file poop */
+ if (LOCAL->filetime && !(LOCAL->mustcheck || LOCAL->shouldcheck) &&
+ (LOCAL->filetime < sbuf.st_mtime)) LOCAL->shouldcheck = T;
+ /* check for changed message status */
+ if (LOCAL->mustcheck || LOCAL->shouldcheck) {
+ LOCAL->filetime = sbuf.st_mtime;
+ if (LOCAL->shouldcheck) /* babble when we do this unilaterally */
+ MM_NOTIFY (stream,"[CHECK] Checking for flag updates",NIL);
+ while (i <= stream->nmsgs) mtx_elt (stream,i++);
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ }
+ /* get shared parse/append permission */
+ if ((sbuf.st_size != LOCAL->filesize) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) {
+ /* parse resulting mailbox */
+ r = (mtx_parse (stream)) ? T : NIL;
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ if (LOCAL) { /* stream must still be alive */
+ /* snarf if this is a read-write inbox */
+ if (stream->inbox && !stream->rdonly) {
+ mtx_snarf (stream);
+ fstat (LOCAL->fd,&sbuf);/* see if file changed now */
+ if ((sbuf.st_size != LOCAL->filesize) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) {
+ /* parse resulting mailbox */
+ r = (mtx_parse (stream)) ? T : NIL;
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ }
+ }
+ }
+ return r; /* return result of the parse */
+}
+
+
+/* MTX mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void mtx_check (MAILSTREAM *stream)
+{
+ /* mark that a check is desired */
+ if (LOCAL) LOCAL->mustcheck = T;
+ if (mtx_ping (stream)) MM_LOG ("Check completed",(long) NIL);
+}
+
+/* MTX mail snarf messages from system inbox
+ * Accepts: MAIL stream
+ */
+
+void mtx_snarf (MAILSTREAM *stream)
+{
+ unsigned long i = 0;
+ unsigned long j,r,hdrlen,txtlen;
+ struct stat sbuf;
+ char *hdr,*txt,lock[MAILTMPLEN],tmp[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ MAILSTREAM *sysibx = NIL;
+ int ld;
+ /* give up if can't get exclusive permission */
+ if ((time (0) >= (LOCAL->lastsnarf +
+ (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) &&
+ strcmp (sysinbox (),stream->mailbox) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) >= 0)) {
+ MM_CRITICAL (stream); /* go critical */
+ /* sizes match and anything in sysinbox? */
+ if (!stat (sysinbox (),&sbuf) && sbuf.st_size &&
+ !fstat (LOCAL->fd,&sbuf) && (sbuf.st_size == LOCAL->filesize) &&
+ (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
+ (!sysibx->rdonly) && (r = sysibx->nmsgs)) {
+ /* yes, go to end of file in our mailbox */
+ lseek (LOCAL->fd,sbuf.st_size,L_SET);
+ /* for each message in sysibx mailbox */
+ while (r && (++i <= sysibx->nmsgs)) {
+ /* snarf message from system INBOX */
+ hdr = cpystr (mail_fetchheader_full (sysibx,i,NIL,&hdrlen,NIL));
+ txt = mail_fetchtext_full (sysibx,i,&txtlen,FT_PEEK);
+ /* if have a message */
+ if (j = hdrlen + txtlen) {
+ /* calculate header line */
+ mail_date (LOCAL->buf,elt = mail_elt (sysibx,i));
+ sprintf (LOCAL->buf + strlen (LOCAL->buf),
+ ",%lu;0000000000%02o\015\012",j,(unsigned)
+ ((fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ /* copy message */
+ if ((write (LOCAL->fd,LOCAL->buf,strlen (LOCAL->buf)) < 0) ||
+ (write (LOCAL->fd,hdr,hdrlen) < 0) ||
+ (write (LOCAL->fd,txt,txtlen) < 0)) r = 0;
+ }
+ fs_give ((void **) &hdr);
+ }
+
+ /* make sure all the updates take */
+ if (fsync (LOCAL->fd)) r = 0;
+ if (r) { /* delete all the messages we copied */
+ if (r == 1) strcpy (tmp,"1");
+ else sprintf (tmp,"1:%lu",r);
+ mail_flag (sysibx,tmp,"\\Deleted",ST_SET);
+ mail_expunge (sysibx); /* now expunge all those messages */
+ }
+ else {
+ sprintf (LOCAL->buf,"Can't copy new mail: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,WARN);
+ ftruncate (LOCAL->fd,sbuf.st_size);
+ }
+ fstat (LOCAL->fd,&sbuf); /* yes, get current file size */
+ LOCAL->filetime = sbuf.st_mtime;
+ }
+ if (sysibx) mail_close (sysibx);
+ MM_NOCRITICAL (stream); /* release critical */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ LOCAL->lastsnarf = time (0);/* note time of last snarf */
+ }
+}
+
+/* MTX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long mtx_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ time_t tp[2];
+ struct stat sbuf;
+ off_t pos = 0;
+ int ld;
+ unsigned long i = 1;
+ unsigned long j,k,m,recent;
+ unsigned long n = 0;
+ unsigned long delta = 0;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (!(ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ mtx_ping (stream))); /* parse sequence if given, ping stream */
+ else if (stream->rdonly) MM_LOG ("Expunge ignored on readonly mailbox",WARN);
+ else {
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ }
+ /* The cretins who designed flock() created a window of vulnerability in
+ * upgrading locks from shared to exclusive or downgrading from exclusive
+ * to shared. Rather than maintain the lock at shared status at a minimum,
+ * flock() actually *releases* the former lock. Obviously they never talked
+ * to any database guys. Fortunately, we have the parse/append permission
+ * lock. If we require this lock before going exclusive on the mailbox,
+ * another process can not sneak in and steal the exclusive mailbox lock on
+ * us, because it will block on trying to get parse/append permission first.
+ */
+ /* get exclusive parse/append permission */
+ if ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) < 0)
+ MM_LOG ("Unable to lock expunge mailbox",ERROR);
+ /* make sure see any newly-arrived messages */
+ else if (!mtx_parse (stream));
+ /* get exclusive access */
+ else if (flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH);/* recover previous lock */
+ (*bn) (BLOCK_NONE,NIL);
+ MM_LOG ("Can't expunge because mailbox is in use by another process",
+ ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+
+ else {
+ MM_CRITICAL (stream); /* go critical */
+ recent = stream->recent; /* get recent now that pinged and locked */
+ /* for each message */
+ while (i <= stream->nmsgs) {
+ /* get cache element */
+ elt = mtx_elt (stream,i);
+ /* number of bytes to smash or preserve */
+ k = elt->private.special.text.size + elt->rfc822_size;
+ /* if need to expunge this message */
+ if (elt->deleted && (sequence ? elt->sequence : T)) {
+ /* if recent, note one less recent message */
+ if (elt->recent) --recent;
+ delta += k; /* number of bytes to delete */
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else if (i++ && delta) {/* preserved message */
+ /* first byte to preserve */
+ j = elt->private.special.offset;
+ do { /* read from source position */
+ m = min (k,LOCAL->buflen);
+ lseek (LOCAL->fd,j,L_SET);
+ read (LOCAL->fd,LOCAL->buf,m);
+ pos = j - delta; /* write to destination position */
+ lseek (LOCAL->fd,pos,L_SET);
+ while (T) {
+ lseek (LOCAL->fd,pos,L_SET);
+ if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ pos += m; /* new position */
+ j += m; /* next chunk, perhaps */
+ } while (k -= m); /* until done */
+ /* note the new address of this text */
+ elt->private.special.offset -= delta;
+ }
+ /* preserved but no deleted messages */
+ else pos = elt->private.special.offset + k;
+ }
+ if (n) { /* truncate file after last message */
+ if (pos != (LOCAL->filesize -= delta)) {
+ sprintf (LOCAL->buf,
+ "Calculated size mismatch %lu != %lu, delta = %lu",
+ (unsigned long) pos,(unsigned long) LOCAL->filesize,delta);
+ MM_LOG (LOCAL->buf,WARN);
+ LOCAL->filesize = pos;/* fix it then */
+ }
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ sprintf (LOCAL->buf,"Expunged %lu messages",n);
+ /* output the news */
+ MM_LOG (LOCAL->buf,(long) NIL);
+ }
+ else MM_LOG ("No messages deleted, so no update needed",(long) NIL);
+ fsync (LOCAL->fd); /* force disk update */
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* reset atime to now */
+ utime (stream->mailbox,tp);
+ MM_NOCRITICAL (stream); /* release critical */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH);/* allow sharers again */
+ (*bn) (BLOCK_NONE,NIL);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+ }
+ return ret;
+}
+
+/* MTX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ time_t tp[2];
+ MESSAGECACHE *elt;
+ unsigned long i,j,k;
+ long ret = LONGT;
+ int fd,ld;
+ char file[MAILTMPLEN],lock[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ /* make sure valid mailbox */
+ if (!mtx_isvalid (mailbox,LOCAL->buf)) switch (errno) {
+ case ENOENT: /* no such file? */
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid MTX-format mailbox name: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a MTX-format mailbox: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* got file? */
+ if ((fd = open (mtx_file (file,mailbox),O_RDWR,NIL)) < 0) {
+ sprintf (LOCAL->buf,"Unable to open copy mailbox: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ MM_CRITICAL (stream); /* go critical */
+ /* get exclusive parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) {
+ MM_LOG ("Unable to lock copy mailbox",ERROR);
+ MM_NOCRITICAL (stream);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
+
+ /* for each requested message */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ /* number of bytes to copy */
+ k = elt->private.special.text.size + elt->rfc822_size;
+ do { /* read from source position */
+ j = min (k,LOCAL->buflen);
+ read (LOCAL->fd,LOCAL->buf,j);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ } while (ret && (k -= j));/* until done */
+ }
+ /* make sure all the updates take */
+ if (!(ret && (ret = !fsync (fd)))) {
+ sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ }
+ if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */
+ /* else preserve \Marked status */
+ else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
+ tp[1] = sbuf.st_mtime; /* preserve mtime */
+ utime (file,tp); /* set the times */
+ close (fd); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ MM_NOCRITICAL (stream); /* release critical */
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE)) {
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mtx_elt (stream,i))->sequence) {
+ elt->deleted = T; /* mark message deleted */
+ /* recalculate status */
+ mtx_update_status (stream,i,NIL);
+ }
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* make sure atime remains greater */
+ utime (stream->mailbox,tp);
+ }
+ }
+ if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
+ MM_LOG ("Can not return meaningful COPYUID with this mailbox format",WARN);
+ return ret;
+}
+
+/* MTX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd,ld,c;
+ char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ time_t tp[2];
+ FILE *df;
+ MESSAGECACHE elt;
+ long f;
+ unsigned long i,uf;
+ STRING *message;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = user_flags (&mtxproto);
+ /* make sure valid mailbox */
+ if (!mtx_isvalid (mailbox,tmp)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!compare_cstring (mailbox,"INBOX")) dummy_create (NIL,"INBOX.MTX");
+ else {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* falls through */
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid MTX-format mailbox name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MTX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get first message */
+ if (!MM_APPEND (af) (stream,data,&flags,&date,&message)) return NIL;
+
+ /* open destination mailbox */
+ if (((fd = open (mtx_file (file,mailbox),O_WRONLY|O_APPEND,NIL)) < 0) ||
+ !(df = fdopen (fd,"ab"))) {
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) {
+ MM_LOG ("Unable to lock append mailbox",ERROR);
+ close (fd);
+ return NIL;
+ }
+ MM_CRITICAL (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ errno = 0;
+ do { /* parse flags */
+ if (!SIZE (message)) { /* guard against zero-length */
+ MM_LOG ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ f = mail_parse_flags (stream,flags,&i);
+ /* reverse bits (dontcha wish we had CIRC?) */
+ for (uf = 0; i; uf |= 1 << (29 - find_rightmost_bit (&i)));
+ if (date) { /* parse date if given */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* mark failure */
+ break;
+ }
+ mail_date (tmp,&elt); /* write preseved date */
+ }
+ else internal_date (tmp); /* get current date in IMAP format */
+ /* write header */
+ if (fprintf (df,"%s,%lu;%010lo%02lo\015\012",tmp,i = SIZE (message),uf,
+ (unsigned long) f) < 0) ret = NIL;
+ else { /* write message */
+ if (i) do c = 0xff & SNX (message);
+ while ((putc (c,df) != EOF) && --i);
+ /* get next message */
+ if (i || !MM_APPEND (af) (stream,data,&flags,&date,&message)) ret = NIL;
+ }
+ } while (ret && message);
+ /* if error... */
+ if (!ret || (fflush (df) == EOF)) {
+ ftruncate (fd,sbuf.st_size);/* revert file */
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ if (errno) {
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ ret = NIL;
+ }
+ if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */
+ /* else preserve \Marked status */
+ else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
+ tp[1] = sbuf.st_mtime; /* preserve mtime */
+ utime (file,tp); /* set the times */
+ fclose (df); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ MM_NOCRITICAL (stream); /* release critical */
+ if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
+ MM_LOG ("Can not return meaningful APPENDUID with this mailbox format",
+ WARN);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* MTX mail generate file string
+ * Accepts: temporary buffer to write into
+ * mailbox name string
+ * Returns: local file string or NIL if failure
+ */
+
+char *mtx_file (char *dst,char *name)
+{
+ char tmp[MAILTMPLEN];
+ char *s = mailboxfile (dst,name);
+ /* return our standard inbox */
+ return (s && !*s) ? mailboxfile (dst,mtx_isvalid ("~/INBOX",tmp) ?
+ "~/INBOX" : "INBOX.MTX") : s;
+}
+
+/* MTX mail parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long mtx_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = NIL;
+ unsigned char c,*s,*t,*x;
+ char tmp[MAILTMPLEN];
+ unsigned long i,j;
+ long curpos = LOCAL->filesize;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ short added = NIL;
+ short silent = stream->silent;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %lu to %lu!",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ stream->silent = T; /* don't pass up exists events yet */
+ while (sbuf.st_size - curpos){/* while there is stuff to parse */
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,L_SET);
+ if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
+ sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size,
+ i ? strerror (errno) : "no data read");
+ MM_LOG (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
+ if (!((s = strchr (LOCAL->buf,'\015')) && (s[1] == '\012'))) {
+ sprintf (tmp,"Unable to find CRLF at %lu in %lu bytes, text: %s",
+ (unsigned long) curpos,i,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ *s = '\0'; /* tie off header line */
+ i = (s + 2) - LOCAL->buf; /* note start of text offset */
+ if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
+ sprintf (tmp,"Unable to parse internal header at %lu: %s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ *s++ = '\0'; *t++ = '\0'; /* tie off fields */
+
+ added = T; /* note that a new message was added */
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ /* instantiate an elt for this message */
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ elt->private.uid = ++stream->uid_last;
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* in case error */
+ elt->private.special.text.size = 0;
+ /* header size not known yet */
+ elt->private.msg.header.text.size = 0;
+ x = s; /* parse the header components */
+ if (mail_parse_date (elt,LOCAL->buf) &&
+ (elt->rfc822_size = strtoul (s,(char **) &s,10)) && (!(s && *s)) &&
+ isdigit (t[0]) && isdigit (t[1]) && isdigit (t[2]) &&
+ isdigit (t[3]) && isdigit (t[4]) && isdigit (t[5]) &&
+ isdigit (t[6]) && isdigit (t[7]) && isdigit (t[8]) &&
+ isdigit (t[9]) && isdigit (t[10]) && isdigit (t[11]) && !t[12])
+ elt->private.special.text.size = i;
+ else { /* oops */
+ sprintf (tmp,"Unable to parse internal header elements at %ld: %s,%s;%s",
+ curpos,(char *) LOCAL->buf,(char *) x,(char *) t);
+ MM_LOG (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ /* make sure didn't run off end of file */
+ if ((curpos += (elt->rfc822_size + i)) > sbuf.st_size) {
+ sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
+ elt->private.special.offset,(unsigned long) curpos,
+ (unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR);
+ mtx_close (stream,NIL);
+ return NIL;
+ }
+ c = t[10]; /* remember first system flags byte */
+ t[10] = '\0'; /* tie off flags */
+ j = strtoul (t,NIL,8); /* get user flags value */
+ t[10] = c; /* restore first system flags byte */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ /* calculate system flags */
+ if ((j = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T;
+ if (j & fDELETED) elt->deleted = T;
+ if (j & fFLAGGED) elt->flagged = T;
+ if (j & fANSWERED) elt->answered = T;
+ if (j & fDRAFT) elt->draft = T;
+ if (!(j & fOLD)) { /* newly arrived message? */
+ elt->recent = T;
+ recent++; /* count up a new recent message */
+ /* mark it as old */
+ mtx_update_status (stream,nmsgs,NIL);
+ }
+ }
+ fsync (LOCAL->fd); /* make sure all the fOLD flags take */
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
+ LOCAL->filetime = sbuf.st_mtime;
+ if (added && !stream->rdonly){/* make sure atime updated */
+ time_t tp[2];
+ tp[0] = time (0);
+ tp[1] = LOCAL->filetime;
+ utime (stream->mailbox,tp);
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return LONGT; /* return the winnage */
+}
+
+/* MTX get cache element with status updating from file
+ * Accepts: MAIL stream
+ * message number
+ * Returns: cache element
+ */
+
+MESSAGECACHE *mtx_elt (MAILSTREAM *stream,unsigned long msgno)
+{
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
+ old.answered = elt->answered; old.draft = elt->draft;
+ old.user_flags = elt->user_flags;
+ mtx_read_flags (stream,elt);
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
+ (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
+ MM_FLAGS (stream,msgno); /* let top level know */
+ return elt;
+}
+
+/* MTX read flags from file
+ * Accepts: MAIL stream
+ * Returns: cache element
+ */
+
+void mtx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ unsigned long i,j;
+ /* noop if readonly and have valid flags */
+ if (stream->rdonly && elt->valid) return;
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 14,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,12) < 0) {
+ sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ /* calculate system flags */
+ i = (((LOCAL->buf[10]-'0') * 8) + LOCAL->buf[11]-'0');
+ elt->seen = i & fSEEN ? T : NIL; elt->deleted = i & fDELETED ? T : NIL;
+ elt->flagged = i & fFLAGGED ? T : NIL;
+ elt->answered = i & fANSWERED ? T : NIL; elt->draft = i & fDRAFT ? T : NIL;
+ LOCAL->buf[10] = '\0'; /* tie off flags */
+ j = strtoul(LOCAL->buf,NIL,8);/* get user flags value */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ elt->valid = T; /* have valid flags now */
+}
+
+/* MTX update status string
+ * Accepts: MAIL stream
+ * message number
+ * flag saying whether or not to sync
+ */
+
+void mtx_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag)
+{
+ time_t tp[2];
+ struct stat sbuf;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ unsigned long j,k = 0;
+ /* readonly */
+ if (stream->rdonly || !elt->valid) mtx_read_flags (stream,elt);
+ else { /* readwrite */
+ j = elt->user_flags; /* get user flags */
+ /* reverse bits (dontcha wish we had CIRC?) */
+ while (j) k |= 1 << (29 - find_rightmost_bit (&j));
+ /* print new flag string */
+ sprintf (LOCAL->buf,"%010lo%02o",k,(unsigned)
+ (fOLD + (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ /* get to that place in the file */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 14,L_SET);
+ /* write new flags */
+ write (LOCAL->fd,LOCAL->buf,12);
+ if (syncflag) { /* sync if requested */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* make sure read is later */
+ utime (stream->mailbox,tp);
+ }
+ }
+}
+
+/* MTX locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * Returns: position of header in file
+ */
+
+unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size)
+{
+ unsigned long siz;
+ long i = 0;
+ int q = 0;
+ char *s,tmp[MAILTMPLEN];
+ MESSAGECACHE *elt = mtx_elt (stream,msgno);
+ unsigned long ret = elt->private.special.offset +
+ elt->private.special.text.size;
+ /* is header size known? */
+ if (!(*size = elt->private.msg.header.text.size)) {
+ lseek (LOCAL->fd,ret,L_SET);/* get to header position */
+ /* search message for CRLF CRLF */
+ for (siz = 1,s = tmp; siz <= elt->rfc822_size; siz++) {
+ /* read another buffer as necessary */
+ if ((--i <= 0) && /* buffer empty? */
+ (read (LOCAL->fd,s = tmp,
+ i = min (elt->rfc822_size - siz,(long) MAILTMPLEN)) < 0))
+ return ret; /* I/O error? */
+ switch (q) { /* sniff at buffer */
+ case 0: /* first character */
+ q = (*s++ == '\015') ? 1 : 0;
+ break;
+ case 1: /* second character */
+ q = (*s++ == '\012') ? 2 : 0;
+ break;
+ case 2: /* third character */
+ q = (*s++ == '\015') ? 3 : 0;
+ break;
+ case 3: /* fourth character */
+ if (*s++ == '\012') { /* have the sequence? */
+ /* yes, note for later */
+ elt->private.msg.header.text.size = *size = siz;
+ return ret;
+ }
+ q = 0; /* lost... */
+ break;
+ }
+ }
+ /* header consumes entire message */
+ elt->private.msg.header.text.size = *size = elt->rfc822_size;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/unix/mx.c b/imap/src/osdep/unix/mx.c
new file mode 100644
index 00000000..45495279
--- /dev/null
+++ b/imap/src/osdep/unix/mx.c
@@ -0,0 +1,1287 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: MX mail routines
+ *
+ * Author(s): Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 3 May 1996
+ * Last Edited: 6 January 2008
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "misc.h"
+#include "dummy.h"
+#include "fdstring.h"
+
+/* Index file */
+
+#define MXINDEXNAME "/.mxindex"
+#define MXINDEX(d,s) strcat (mx_file (d,s),MXINDEXNAME)
+
+
+/* MX I/O stream local data */
+
+typedef struct mx_local {
+ int fd; /* file descriptor of open index */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned long cachedtexts; /* total size of all cached texts */
+ time_t scantime; /* last time directory scanned */
+} MXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((MXLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *mx_valid (char *name);
+int mx_isvalid (char *name,char *tmp);
+int mx_namevalid (char *name);
+void *mx_parameters (long function,void *value);
+long mx_dirfmttest (char *name);
+void mx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+long mx_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz);
+void mx_list (MAILSTREAM *stream,char *ref,char *pat);
+void mx_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long mx_subscribe (MAILSTREAM *stream,char *mailbox);
+long mx_unsubscribe (MAILSTREAM *stream,char *mailbox);
+long mx_create (MAILSTREAM *stream,char *mailbox);
+long mx_delete (MAILSTREAM *stream,char *mailbox);
+long mx_rename (MAILSTREAM *stream,char *old,char *newname);
+int mx_rename_work (char *src,size_t srcl,char *dst,size_t dstl,char *name);
+MAILSTREAM *mx_open (MAILSTREAM *stream);
+void mx_close (MAILSTREAM *stream,long options);
+void mx_fast (MAILSTREAM *stream,char *sequence,long flags);
+char *mx_fast_work (MAILSTREAM *stream,MESSAGECACHE *elt);
+char *mx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags);
+long mx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void mx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void mx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long mx_ping (MAILSTREAM *stream);
+void mx_check (MAILSTREAM *stream);
+long mx_expunge (MAILSTREAM *stream,char *sequence,long options);
+long mx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,
+ long options);
+long mx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+long mx_append_msg (MAILSTREAM *stream,char *flags,MESSAGECACHE *elt,
+ STRING *st,SEARCHSET *set);
+
+int mx_select (struct direct *name);
+int mx_numsort (const void *d1,const void *d2);
+char *mx_file (char *dst,char *name);
+long mx_lockindex (MAILSTREAM *stream);
+void mx_unlockindex (MAILSTREAM *stream);
+void mx_setdate (char *file,MESSAGECACHE *elt);
+
+
+/* MX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mxdriver = {
+ "mx", /* driver name */
+ /* driver flags */
+ DR_MAIL|DR_LOCAL|DR_NOFAST|DR_CRLF|DR_LOCKING|DR_DIRFMT,
+ (DRIVER *) NIL, /* next driver */
+ mx_valid, /* mailbox is valid for us */
+ mx_parameters, /* manipulate parameters */
+ mx_scan, /* scan mailboxes */
+ mx_list, /* find mailboxes */
+ mx_lsub, /* find subscribed mailboxes */
+ mx_subscribe, /* subscribe to mailbox */
+ mx_unsubscribe, /* unsubscribe from mailbox */
+ mx_create, /* create mailbox */
+ mx_delete, /* delete mailbox */
+ mx_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ mx_open, /* open mailbox */
+ mx_close, /* close mailbox */
+ mx_fast, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ mx_header, /* fetch message header only */
+ mx_text, /* fetch message body only */
+ NIL, /* fetch partial message test */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ mx_flag, /* modify flags */
+ mx_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mx_ping, /* ping mailbox to see if still alive */
+ mx_check, /* check for new messages */
+ mx_expunge, /* expunge deleted messages */
+ mx_copy, /* copy messages to another mailbox */
+ mx_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mxproto = {&mxdriver};
+
+/* MX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mx_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return mx_isvalid (name,tmp) ? &mxdriver : NIL;
+}
+
+
+/* MX mail test for valid mailbox
+ * Accepts: mailbox name
+ * temporary buffer to use
+ * Returns: T if valid, NIL otherwise with errno holding dir stat error
+ */
+
+int mx_isvalid (char *name,char *tmp)
+{
+ struct stat sbuf;
+ errno = NIL; /* zap error */
+ if ((strlen (name) <= NETMAXMBX) && *mx_file (tmp,name) &&
+ !stat (tmp,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
+ /* name is directory; is it mx? */
+ if (!stat (MXINDEX (tmp,name),&sbuf) &&
+ ((sbuf.st_mode & S_IFMT) == S_IFREG)) return T;
+ errno = NIL; /* directory but not mx */
+ }
+ else if (!compare_cstring (name,"INBOX")) errno = NIL;
+ return NIL;
+}
+
+
+/* MX mail test for valid mailbox
+ * Accepts: mailbox name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int mx_namevalid (char *name)
+{
+ char *s = (*name == '/') ? name + 1 : name;
+ while (s && *s) { /* make sure valid name */
+ if (isdigit (*s)) s++; /* digit, check this node further... */
+ else if (*s == '/') break; /* all digit node, barf */
+ /* non-digit, skip to next node or return */
+ else if (!((s = strchr (s+1,'/')) && *++s)) return T;
+ }
+ return NIL; /* all numeric or empty node */
+}
+
+/* MX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *mx_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = mailboxfile ((char *) value,"~/INBOX");
+ break;
+ case GET_DIRFMTTEST:
+ ret = (void *) mx_dirfmttest;
+ break;
+ case GET_SCANCONTENTS:
+ ret = (void *) mx_scan_contents;
+ break;
+ }
+ return ret;
+}
+
+
+/* MX test for directory format internal node
+ * Accepts: candidate node name
+ * Returns: T if internal name, NIL otherwise
+ */
+
+long mx_dirfmttest (char *name)
+{
+ int c;
+ /* success if index name or all-numberic */
+ if (strcmp (name,MXINDEXNAME+1))
+ while (c = *name++) if (!isdigit (c)) return NIL;
+ return LONGT;
+}
+
+/* MX scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void mx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* MX scan mailbox for contents
+ * Accepts: mailbox name
+ * desired contents
+ * contents size
+ * file size (ignored)
+ * Returns: NIL if contents not found, T if found
+ */
+
+long mx_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz)
+{
+ long i,nfiles;
+ void *a;
+ char *s;
+ long ret = NIL;
+ size_t namelen = strlen (name);
+ struct stat sbuf;
+ struct direct **names = NIL;
+ if ((nfiles = scandir (name,&names,mx_select,mx_numsort)) > 0)
+ for (i = 0; i < nfiles; ++i) {
+ if (!ret) {
+ sprintf (s = (char *) fs_get (namelen + strlen (names[i]->d_name) + 2),
+ "%s/%s",name,names[i]->d_name);
+ if (!stat (s,&sbuf) && (csiz <= sbuf.st_size))
+ ret = dummy_scan_contents (s,contents,csiz,sbuf.st_size);
+ fs_give ((void **) &s);
+ }
+ fs_give ((void **) &names[i]);
+ }
+ /* free directory list */
+ if (a = (void *) names) fs_give ((void **) &a);
+ return ret;
+}
+
+/* MX list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mx_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* MX list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void mx_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* MX mail subscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to add to subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long mx_subscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return sm_subscribe (mailbox);
+}
+
+
+/* MX mail unsubscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to delete from subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long mx_unsubscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return sm_unsubscribe (mailbox);
+}
+
+/* MX mail create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mx_create (MAILSTREAM *stream,char *mailbox)
+{
+ DRIVER *test;
+ int fd;
+ char *s,tmp[MAILTMPLEN];
+ int mask = umask (0);
+ long ret = NIL;
+ if (!mx_namevalid (mailbox)) /* validate name */
+ sprintf (tmp,"Can't create mailbox %.80s: invalid MX-format name",mailbox);
+ /* must not already exist */
+ else if ((test = mail_valid (NIL,mailbox,NIL)) &&
+ strcmp (test->name,"dummy"))
+ sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox);
+ /* create directory */
+ else if (!dummy_create_path (stream,MXINDEX (tmp,mailbox),
+ get_dir_protection (mailbox)))
+ sprintf (tmp,"Can't create mailbox %.80s: %s",mailbox,strerror (errno));
+ else { /* success */
+ /* set index protection */
+ set_mbx_protections (mailbox,tmp);
+ /* tie off directory name */
+ *(s = strrchr (tmp,'/') + 1) = '\0';
+ /* set directory protection */
+ set_mbx_protections (mailbox,tmp);
+ ret = LONGT;
+ }
+ umask (mask); /* restore mask */
+ if (!ret) MM_LOG (tmp,ERROR); /* some error */
+ return ret;
+}
+
+/* MX mail delete mailbox
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mx_delete (MAILSTREAM *stream,char *mailbox)
+{
+ DIR *dirp;
+ struct direct *d;
+ char *s;
+ char tmp[MAILTMPLEN];
+ if (!mx_isvalid (mailbox,tmp))
+ sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox);
+ /* delete index */
+ else if (unlink (MXINDEX (tmp,mailbox)))
+ sprintf (tmp,"Can't delete mailbox %.80s index: %s",
+ mailbox,strerror (errno));
+ else { /* get directory name */
+ *(s = strrchr (tmp,'/')) = '\0';
+ if (dirp = opendir (tmp)) { /* open directory */
+ *s++ = '/'; /* restore delimiter */
+ /* massacre messages */
+ while (d = readdir (dirp)) if (mx_select (d)) {
+ strcpy (s,d->d_name); /* make path */
+ unlink (tmp); /* sayonara */
+ }
+ closedir (dirp); /* flush directory */
+ *(s = strrchr (tmp,'/')) = '\0';
+ if (rmdir (tmp)) { /* try to remove the directory */
+ sprintf (tmp,"Can't delete name %.80s: %s",mailbox,strerror (errno));
+ MM_LOG (tmp,WARN);
+ }
+ }
+ return T; /* always success */
+ }
+ MM_LOG (tmp,ERROR); /* something failed */
+ return NIL;
+}
+
+/* MX mail rename mailbox
+ * Accepts: MX mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long mx_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN];
+ struct stat sbuf;
+ if (!mx_isvalid (old,tmp))
+ sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old);
+ else if (!mx_namevalid (newname))
+ sprintf (tmp,"Can't rename to mailbox %.80s: invalid MX-format name",
+ newname);
+ /* new mailbox name must not be valid */
+ else if (mx_isvalid (newname,tmp))
+ sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists",
+ newname);
+ else {
+ mx_file (tmp,old); /* build old directory name */
+ mx_file (tmp1,newname); /* and new directory name */
+ /* easy if not INBOX */
+ if (compare_cstring (old,"INBOX")) {
+ /* found superior to destination name? */
+ if (s = strrchr (mx_file (tmp1,newname),'/')) {
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp1,get_dir_protection (newname)))
+ return NIL;
+ *s = c; /* restore full name */
+ }
+ if (!rename (tmp,tmp1)) return LONGT;
+ }
+
+ /* RFC 3501 requires this */
+ else if (dummy_create_path (stream,strcat (tmp1,"/"),
+ get_dir_protection (newname))) {
+ void *a;
+ int i,n,lasterror;
+ struct direct **names = NIL;
+ size_t srcl = strlen (tmp);
+ size_t dstl = strlen (tmp1);
+ /* rename each mx file to new directory */
+ for (i = lasterror = 0,n = scandir (tmp,&names,mx_select,mx_numsort);
+ i < n; ++i) {
+ if (mx_rename_work (tmp,srcl,tmp1,dstl,names[i]->d_name))
+ lasterror = errno;
+ fs_give ((void **) &names[i]);
+ }
+ /* free directory list */
+ if (a = (void *) names) fs_give ((void **) &a);
+ if (lasterror || mx_rename_work (tmp,srcl,tmp1,dstl,MXINDEXNAME+1))
+ errno = lasterror;
+ else return mx_create (NIL,"INBOX");
+ }
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",
+ old,newname,strerror (errno));
+ }
+ MM_LOG (tmp,ERROR); /* something failed */
+ return NIL;
+}
+
+
+/* MX rename worker routine
+ * Accepts: source directory name
+ * source directory name length
+ * destination directory name
+ * destination directory name length
+ * name of node to move
+ * Returns: zero if success, non-zero if error
+ */
+
+int mx_rename_work (char *src,size_t srcl,char *dst,size_t dstl,char *name)
+{
+ int ret;
+ size_t len = strlen (name);
+ char *s = (char *) fs_get (srcl + len + 2);
+ char *d = (char *) fs_get (dstl + len + 1);
+ sprintf (s,"%s/%s",src,name);
+ sprintf (d,"%s%s",dst,name);
+ ret = rename (s,d);
+ fs_give ((void **) &s);
+ fs_give ((void **) &d);
+ return ret;
+}
+
+/* MX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mx_open (MAILSTREAM *stream)
+{
+ char tmp[MAILTMPLEN];
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&mxproto);
+ if (stream->local) fatal ("mx recycle stream");
+ stream->local = fs_get (sizeof (MXLOCAL));
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ mx_file (tmp,stream->mailbox);/* get directory name */
+ /* canonicalize mailbox name */
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ /* make temporary buffer */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ LOCAL->scantime = 0; /* not scanned yet */
+ LOCAL->fd = -1; /* no index yet */
+ LOCAL->cachedtexts = 0; /* no cached texts */
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (mx_ping (stream) && !(stream->nmsgs || stream->silent))
+ MM_LOG ("Mailbox is empty",(long) NIL);
+ stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
+ stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ stream->kwd_create = (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ?
+ NIL : T; /* can we create new user flags? */
+ return stream; /* return stream to caller */
+}
+
+/* MX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void mx_close (MAILSTREAM *stream,long options)
+{
+ if (LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ if (options & CL_EXPUNGE) mx_expunge (stream,NIL,NIL);
+ /* free local scratch buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ stream->silent = silent; /* reset silent state */
+ }
+}
+
+/* MX mail fetch fast information
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ */
+
+void mx_fast (MAILSTREAM *stream,char *sequence,long flags)
+{
+ unsigned long i;
+ MESSAGECACHE *elt;
+ if (stream && LOCAL &&
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) mx_fast_work (stream,elt);
+}
+
+
+/* MX mail fetch fast information
+ * Accepts: MAIL stream
+ * message cache element
+ * Returns: name of message file
+ */
+
+char *mx_fast_work (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ struct stat sbuf;
+ struct tm *tm;
+ /* build message file name */
+ sprintf (LOCAL->buf,"%s/%lu",stream->mailbox,elt->private.uid);
+ /* have size yet? */
+ if (!elt->rfc822_size && !stat (LOCAL->buf,&sbuf)) {
+ /* make plausible IMAPish date string */
+ tm = gmtime (&sbuf.st_mtime);
+ elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
+ elt->year = tm->tm_year + 1900 - BASEYEAR;
+ elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
+ elt->seconds = tm->tm_sec;
+ elt->zhours = 0; elt->zminutes = 0; elt->zoccident = 0;
+ elt->rfc822_size = sbuf.st_size;
+ }
+ return (char *) LOCAL->buf; /* return file name */
+}
+
+/* MX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *mx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
+ long flags)
+{
+ unsigned long i;
+ int fd;
+ MESSAGECACHE *elt;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get elt */
+ if (!elt->private.msg.header.text.data) {
+ /* purge cache if too big */
+ if (LOCAL->cachedtexts > max (stream->nmsgs * 4096,2097152)) {
+ mail_gc (stream,GC_TEXTS);/* just can't keep that much */
+ LOCAL->cachedtexts = 0;
+ }
+ if ((fd = open (mx_fast_work (stream,elt),O_RDONLY,NIL)) < 0) return "";
+ /* is buffer big enough? */
+ if (elt->rfc822_size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = elt->rfc822_size) + 1);
+ }
+ /* slurp message */
+ read (fd,LOCAL->buf,elt->rfc822_size);
+ /* tie off file */
+ LOCAL->buf[elt->rfc822_size] = '\0';
+ close (fd); /* flush message file */
+ /* find end of header */
+ if (elt->rfc822_size < 4) i = 0;
+ else for (i = 4; (i < elt->rfc822_size) &&
+ !((LOCAL->buf[i - 4] == '\015') &&
+ (LOCAL->buf[i - 3] == '\012') &&
+ (LOCAL->buf[i - 2] == '\015') &&
+ (LOCAL->buf[i - 1] == '\012')); i++);
+ /* copy header */
+ cpytxt (&elt->private.msg.header.text,LOCAL->buf,i);
+ cpytxt (&elt->private.msg.text.text,LOCAL->buf+i,elt->rfc822_size - i);
+ /* add to cached size */
+ LOCAL->cachedtexts += elt->rfc822_size;
+ }
+ *length = elt->private.msg.header.text.size;
+ return (char *) elt->private.msg.header.text.data;
+}
+
+/* MX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ unsigned long i;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);
+ /* snarf message if don't have it yet */
+ if (!elt->private.msg.text.text.data) {
+ mx_header (stream,msgno,&i,flags);
+ if (!elt->private.msg.text.text.data) return NIL;
+ }
+ /* mark as seen */
+ if (!(flags & FT_PEEK) && mx_lockindex (stream)) {
+ elt->seen = T;
+ mx_unlockindex (stream);
+ MM_FLAGS (stream,msgno);
+ }
+ INIT (bs,mail_string,elt->private.msg.text.text.data,
+ elt->private.msg.text.text.size);
+ return T;
+}
+
+/* MX mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ */
+
+void mx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ mx_unlockindex (stream); /* finished with index */
+}
+
+
+/* MX per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void mx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ mx_lockindex (stream); /* lock index if not already locked */
+}
+
+/* MX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long mx_ping (MAILSTREAM *stream)
+{
+ MAILSTREAM *sysibx = NIL;
+ MESSAGECACHE *elt,*selt;
+ struct stat sbuf;
+ char *s,tmp[MAILTMPLEN];
+ int fd;
+ unsigned long i,j,r,old;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ int silent = stream->silent;
+ if (stat (stream->mailbox,&sbuf)) return NIL;
+ stream->silent = T; /* don't pass up exists events yet */
+ if (sbuf.st_ctime != LOCAL->scantime) {
+ struct direct **names = NIL;
+ long nfiles = scandir (stream->mailbox,&names,mx_select,mx_numsort);
+ if (nfiles < 0) nfiles = 0; /* in case error */
+ old = stream->uid_last;
+ /* note scanned now */
+ LOCAL->scantime = sbuf.st_ctime;
+ /* scan directory */
+ for (i = 0; i < nfiles; ++i) {
+ /* if newly seen, add to list */
+ if ((j = atoi (names[i]->d_name)) > old) {
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ stream->uid_last = (elt = mail_elt (stream,nmsgs))->private.uid = j;
+ elt->valid = T; /* note valid flags */
+ if (old) { /* other than the first pass? */
+ elt->recent = T; /* yup, mark as recent */
+ recent++; /* bump recent count */
+ }
+ }
+ fs_give ((void **) &names[i]);
+ }
+ /* free directory */
+ if (s = (void *) names) fs_give ((void **) &s);
+ }
+ stream->nmsgs = nmsgs; /* don't upset mail_uid() */
+
+ /* if INBOX, snarf from system INBOX */
+ if (mx_lockindex (stream) && stream->inbox &&
+ !strcmp (sysinbox (),stream->mailbox)) {
+ old = stream->uid_last;
+ MM_CRITICAL (stream); /* go critical */
+ /* see if anything in system inbox */
+ if (!stat (sysinbox (),&sbuf) && sbuf.st_size &&
+ (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
+ !sysibx->rdonly && (r = sysibx->nmsgs)) {
+ for (i = 1; i <= r; ++i) {/* for each message in sysinbox mailbox */
+ /* build file name we will use */
+ sprintf (LOCAL->buf,"%s/%lu",stream->mailbox,++old);
+ /* snarf message from Berkeley mailbox */
+ selt = mail_elt (sysibx,i);
+ if (((fd = open (LOCAL->buf,O_WRONLY|O_CREAT|O_EXCL,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL)))
+ >= 0) &&
+ (s = mail_fetchheader_full (sysibx,i,NIL,&j,FT_PEEK)) &&
+ (write (fd,s,j) == j) &&
+ (s = mail_fetchtext_full (sysibx,i,&j,FT_PEEK)) &&
+ (write (fd,s,j) == j) && !fsync (fd) && !close (fd)) {
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ stream->uid_last = /* create new elt, note its file number */
+ (elt = mail_elt (stream,nmsgs))->private.uid = old;
+ recent++; /* bump recent count */
+ /* set up initial flags and date */
+ elt->valid = elt->recent = T;
+ elt->seen = selt->seen;
+ elt->deleted = selt->deleted;
+ elt->flagged = selt->flagged;
+ elt->answered = selt->answered;
+ elt->draft = selt->draft;
+ elt->day = selt->day;elt->month = selt->month;elt->year = selt->year;
+ elt->hours = selt->hours;elt->minutes = selt->minutes;
+ elt->seconds = selt->seconds;
+ elt->zhours = selt->zhours; elt->zminutes = selt->zminutes;
+ elt->zoccident = selt->zoccident;
+ mx_setdate (LOCAL->buf,elt);
+ sprintf (tmp,"%lu",i);/* delete it from the sysinbox */
+ mail_flag (sysibx,tmp,"\\Deleted",ST_SET);
+ }
+ else { /* failed to snarf */
+ if (fd) { /* did it ever get opened? */
+ close (fd); /* close descriptor */
+ unlink (LOCAL->buf);/* flush this file */
+ }
+ sprintf (tmp,"Message copy to MX mailbox failed: %.80s",
+ s,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ r = 0; /* stop the snarf in its tracks */
+ }
+ }
+ /* update scan time */
+ if (!stat (stream->mailbox,&sbuf)) LOCAL->scantime = sbuf.st_ctime;
+ mail_expunge (sysibx); /* now expunge all those messages */
+ }
+ if (sysibx) mail_close (sysibx);
+ MM_NOCRITICAL (stream); /* release critical */
+ }
+ mx_unlockindex (stream); /* done with index */
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of mailbox size */
+ mail_recent (stream,recent);
+ return T; /* return that we are alive */
+}
+
+/* MX mail check mailbox
+ * Accepts: MAIL stream
+ */
+
+void mx_check (MAILSTREAM *stream)
+{
+ if (mx_ping (stream)) MM_LOG ("Check completed",(long) NIL);
+}
+
+
+/* MX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long mx_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ MESSAGECACHE *elt;
+ unsigned long i = 1;
+ unsigned long n = 0;
+ unsigned long recent = stream->recent;
+ if (ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ mx_lockindex (stream)) { /* lock the index */
+ MM_CRITICAL (stream); /* go critical */
+ while (i <= stream->nmsgs) {/* for each message */
+ elt = mail_elt (stream,i);/* if deleted, need to trash it */
+ if (elt->deleted && (sequence ? elt->sequence : T)) {
+ sprintf (LOCAL->buf,"%s/%lu",stream->mailbox,elt->private.uid);
+ if (unlink (LOCAL->buf)) {/* try to delete the message */
+ sprintf (LOCAL->buf,"Expunge of message %lu failed, aborted: %s",i,
+ strerror (errno));
+ MM_LOG (LOCAL->buf,(long) NIL);
+ break;
+ }
+ /* note uncached */
+ LOCAL->cachedtexts -= ((elt->private.msg.header.text.data ?
+ elt->private.msg.header.text.size : 0) +
+ (elt->private.msg.text.text.data ?
+ elt->private.msg.text.text.size : 0));
+ mail_gc_msg (&elt->private.msg,GC_ENV | GC_TEXTS);
+ if(elt->recent)--recent;/* if recent, note one less recent message */
+ mail_expunged(stream,i);/* notify upper levels */
+ n++; /* count up one more expunged message */
+ }
+ else i++; /* otherwise try next message */
+ }
+ if (n) { /* output the news if any expunged */
+ sprintf (LOCAL->buf,"Expunged %lu messages",n);
+ MM_LOG (LOCAL->buf,(long) NIL);
+ }
+ else MM_LOG ("No messages deleted, so no update needed",(long) NIL);
+ MM_NOCRITICAL (stream); /* release critical */
+ mx_unlockindex (stream); /* finished with index */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ }
+ return ret;
+}
+
+/* MX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long mx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ FDDATA d;
+ STRING st;
+ MESSAGECACHE *elt;
+ MAILSTREAM *astream;
+ struct stat sbuf;
+ int fd;
+ unsigned long i,j,uid,uidv;
+ char *t,tmp[MAILTMPLEN];
+ long ret;
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ /* make sure valid mailbox */
+ if (!mx_valid (mailbox)) switch (errno) {
+ case NIL: /* no error in stat() */
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a MX-format mailbox: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ default: /* some stat() error */
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ }
+ /* copy the messages */
+ if (!(ret = ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))));
+ /* acquire stream to append to */
+ else if (!(astream = mail_open (NIL,mailbox,OP_SILENT))) {
+ MM_LOG ("Can't open copy mailbox",ERROR);
+ ret = NIL;
+ }
+ else {
+ MM_CRITICAL (stream); /* go critical */
+ if (!(ret = mx_lockindex (astream)))
+ MM_LOG ("Message copy failed: unable to lock index",ERROR);
+ else {
+
+ copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ for (i = 1,uid = uidv = 0; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ if (ret = ((fd = open (mx_fast_work (stream,elt),O_RDONLY,NIL))
+ >= 0)) {
+ fstat (fd,&sbuf); /* get size of message */
+ d.fd = fd; /* set up file descriptor */
+ d.pos = 0; /* start of file */
+ d.chunk = LOCAL->buf;
+ d.chunksize = CHUNKSIZE;
+ INIT (&st,fd_string,&d,sbuf.st_size);
+ /* init flag string */
+ tmp[0] = tmp[1] = '\0';
+ if (j = elt->user_flags) do
+ if (t = stream->user_flags[find_rightmost_bit (&j)])
+ strcat (strcat (tmp," "),t);
+ while (j);
+ if (elt->seen) strcat (tmp," \\Seen");
+ if (elt->deleted) strcat (tmp," \\Deleted");
+ if (elt->flagged) strcat (tmp," \\Flagged");
+ if (elt->answered) strcat (tmp," \\Answered");
+ if (elt->draft) strcat (tmp," \\Draft");
+ tmp[0] = '('; /* open list */
+ strcat (tmp,")"); /* close list */
+ if (ret = mx_append_msg (astream,tmp,elt,&st,dest)) {
+ /* add to source set if needed */
+ if (source) mail_append_set (source,mail_uid (stream,i));
+ /* delete if doing a move */
+ if (options & CP_MOVE) elt->deleted = T;
+ }
+ }
+ }
+ /* return sets if doing COPYUID */
+ if (cu && ret) (*cu) (stream,mailbox,astream->uid_validity,source,dest);
+ else { /* flush any sets we may have built */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ mx_unlockindex (astream); /* unlock index */
+ }
+ MM_NOCRITICAL (stream);
+ mail_close (astream); /* finished with append stream */
+ }
+ return ret; /* return success */
+}
+
+/* MX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ MESSAGECACHE elt;
+ MAILSTREAM *astream;
+ char *flags,*date,tmp[MAILTMPLEN];
+ STRING *message;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = user_flags (&mxproto);
+ /* N.B.: can't use LOCAL->buf for tmp */
+ /* make sure valid mailbox */
+ if (!mx_isvalid (mailbox,tmp)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!compare_cstring (mailbox,"INBOX")) mx_create (NIL,"INBOX");
+ else {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* falls through */
+ case 0: /* merely empty file? */
+ break;
+ case EINVAL:
+ sprintf (tmp,"Invalid MX-format mailbox name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a MX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+
+ /* get first message */
+ if (!MM_APPEND (af) (stream,data,&flags,&date,&message)) return NIL;
+ if (!(astream = mail_open (NIL,mailbox,OP_SILENT))) {
+ MM_LOG ("Can't open append mailbox",ERROR);
+ return NIL;
+ }
+ MM_CRITICAL (astream); /* go critical */
+ /* lock the index */
+ if (!(ret = mx_lockindex (astream)))
+ MM_LOG ("Message append failed: unable to lock index",ERROR);
+ else {
+ appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ do {
+ /* guard against zero-length */
+ if (!(ret = SIZE (message)))
+ MM_LOG ("Append of zero-length message",ERROR);
+ else if (date && !(ret = mail_parse_date (&elt,date))) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ }
+ else ret = mx_append_msg (astream,flags,date ? &elt : NIL,message,dst)&&
+ MM_APPEND (af) (stream,data,&flags,&date,&message);
+ } while (ret && message);
+ /* return sets if doing APPENDUID */
+ if (au && ret) (*au) (mailbox,astream->uid_validity,dst);
+ else mail_free_searchset (&dst);
+ mx_unlockindex (astream); /* unlock index */
+ }
+ MM_NOCRITICAL (astream); /* release critical */
+ mail_close (astream);
+ return ret;
+}
+
+/* MX mail append single message
+ * Accepts: MAIL stream
+ * flags for new message if non-NIL
+ * elt with source date if non-NIL
+ * stringstruct of message text
+ * searchset to place UID
+ * Returns: T if success, NIL if failure
+ */
+
+long mx_append_msg (MAILSTREAM *stream,char *flags,MESSAGECACHE *elt,
+ STRING *st,SEARCHSET *set)
+{
+ char tmp[MAILTMPLEN];
+ int fd;
+ unsigned long uf;
+ long f = mail_parse_flags (stream,flags,&uf);
+ /* make message file name */
+ sprintf (tmp,"%s/%lu",stream->mailbox,++stream->uid_last);
+ if ((fd = open (tmp,O_WRONLY|O_CREAT|O_EXCL,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0) {
+ sprintf (tmp,"Can't create append message: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ while (SIZE (st)) { /* copy the file */
+ if (st->cursize && (write (fd,st->curpos,st->cursize) < 0)) {
+ unlink (tmp); /* delete file */
+ close (fd); /* close the file */
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ SETPOS (st,GETPOS (st) + st->cursize);
+ }
+ close (fd); /* close the file */
+ if (elt) mx_setdate (tmp,elt);/* set file date */
+ /* swell the cache */
+ mail_exists (stream,++stream->nmsgs);
+ /* copy flags */
+ mail_append_set (set,(elt = mail_elt (stream,stream->nmsgs))->private.uid =
+ stream->uid_last);
+ if (f&fSEEN) elt->seen = T;
+ if (f&fDELETED) elt->deleted = T;
+ if (f&fFLAGGED) elt->flagged = T;
+ if (f&fANSWERED) elt->answered = T;
+ if (f&fDRAFT) elt->draft = T;
+ elt->user_flags |= uf;
+ return LONGT;
+}
+
+/* Internal routines */
+
+
+/* MX file name selection test
+ * Accepts: candidate directory entry
+ * Returns: T to use file name, NIL to skip it
+ */
+
+int mx_select (struct direct *name)
+{
+ char c;
+ char *s = name->d_name;
+ while (c = *s++) if (!isdigit (c)) return NIL;
+ return T;
+}
+
+
+/* MX file name comparision
+ * Accepts: first candidate directory entry
+ * second candidate directory entry
+ * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
+ */
+
+int mx_numsort (const void *d1,const void *d2)
+{
+ return atoi ((*(struct direct **) d1)->d_name) -
+ atoi ((*(struct direct **) d2)->d_name);
+}
+
+
+/* MX mail build file name
+ * Accepts: destination string
+ * source
+ * Returns: destination
+ */
+
+char *mx_file (char *dst,char *name)
+{
+ char *s;
+ /* empty string if mailboxfile fails */
+ if (!mailboxfile (dst,name)) *dst = '\0';
+ /* driver-selected INBOX */
+ else if (!*dst) mailboxfile (dst,"~/INBOX");
+ /* tie off unnecessary trailing / */
+ else if ((s = strrchr (dst,'/')) && !s[1]) *s = '\0';
+ return dst;
+}
+
+/* MX read and lock index
+ * Accepts: MAIL stream
+ * Returns: T if success, NIL if failure
+ */
+
+long mx_lockindex (MAILSTREAM *stream)
+{
+ unsigned long uf,sf,uid;
+ int k = 0;
+ unsigned long msgno = 1;
+ struct stat sbuf;
+ char *s,*t,*idx,tmp[2*MAILTMPLEN];
+ MESSAGECACHE *elt;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if ((LOCAL->fd < 0) && /* get index file, no-op if already have it */
+ (LOCAL->fd = open (strcat (strcpy (tmp,stream->mailbox),MXINDEXNAME),
+ O_RDWR|O_CREAT,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL)))
+ >= 0) {
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_EX); /* get exclusive lock */
+ (*bn) (BLOCK_NONE,NIL);
+ fstat (LOCAL->fd,&sbuf); /* get size of index */
+ /* slurp index */
+ read (LOCAL->fd,s = idx = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size);
+ idx[sbuf.st_size] = '\0'; /* tie off index */
+ /* parse index */
+ if (sbuf.st_size) while (s && *s) switch (*s) {
+ case 'V': /* UID validity record */
+ stream->uid_validity = strtoul (s+1,&s,16);
+ break;
+ case 'L': /* UID last record */
+ stream->uid_last = strtoul (s+1,&s,16);
+ break;
+ case 'K': /* keyword */
+ /* find end of keyword */
+ if (s = strchr (t = ++s,'\n')) {
+ *s++ = '\0'; /* tie off keyword */
+ /* copy keyword */
+ if ((k < NUSERFLAGS) && !stream->user_flags[k] &&
+ (strlen (t) <= MAXUSERFLAG)) stream->user_flags[k] = cpystr (t);
+ k++; /* one more keyword */
+ }
+ break;
+
+ case 'M': /* message status record */
+ uid = strtoul (s+1,&s,16);/* get UID for this message */
+ if (*s == ';') { /* get user flags */
+ uf = strtoul (s+1,&s,16);
+ if (*s == '.') { /* get system flags */
+ sf = strtoul (s+1,&s,16);
+ while ((msgno <= stream->nmsgs) && (mail_uid (stream,msgno) < uid))
+ msgno++; /* find message number for this UID */
+ if ((msgno <= stream->nmsgs) && (mail_uid (stream,msgno) == uid)) {
+ (elt = mail_elt (stream,msgno))->valid = T;
+ elt->user_flags=uf; /* set user and system flags in elt */
+ if (sf & fSEEN) elt->seen = T;
+ if (sf & fDELETED) elt->deleted = T;
+ if (sf & fFLAGGED) elt->flagged = T;
+ if (sf & fANSWERED) elt->answered = T;
+ if (sf & fDRAFT) elt->draft = T;
+ }
+ break;
+ }
+ }
+ default: /* bad news */
+ sprintf (tmp,"Error in index: %.80s",s);
+ MM_LOG (tmp,ERROR);
+ *s = NIL; /* ignore remainder of index */
+ }
+ else { /* new index */
+ stream->uid_validity = time (0);
+ user_flags (stream); /* init stream with default user flags */
+ }
+ fs_give ((void **) &idx); /* flush index */
+ }
+ return (LOCAL->fd >= 0) ? T : NIL;
+}
+
+/* MX write and unlock index
+ * Accepts: MAIL stream
+ */
+
+#define MXIXBUFLEN 2048
+
+void mx_unlockindex (MAILSTREAM *stream)
+{
+ unsigned long i,j;
+ off_t size = 0;
+ char *s,tmp[MXIXBUFLEN + 64];
+ MESSAGECACHE *elt;
+ if (LOCAL->fd >= 0) {
+ lseek (LOCAL->fd,0,L_SET); /* rewind file */
+ /* write header */
+ sprintf (s = tmp,"V%08lxL%08lx",stream->uid_validity,stream->uid_last);
+ for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; ++i)
+ sprintf (s += strlen (s),"K%s\n",stream->user_flags[i]);
+ /* write messages */
+ for (i = 1; i <= stream->nmsgs; i++) {
+ /* filled buffer? */
+ if (((s += strlen (s)) - tmp) > MXIXBUFLEN) {
+ write (LOCAL->fd,tmp,j = s - tmp);
+ size += j;
+ *(s = tmp) = '\0'; /* dump out and restart buffer */
+ }
+ elt = mail_elt (stream,i);
+ sprintf(s,"M%08lx;%08lx.%04x",elt->private.uid,elt->user_flags,(unsigned)
+ ((fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ }
+ /* write tail end of buffer */
+ if ((s += strlen (s)) != tmp) {
+ write (LOCAL->fd,tmp,j = s - tmp);
+ size += j;
+ }
+ ftruncate (LOCAL->fd,size);
+ flock (LOCAL->fd,LOCK_UN); /* unlock the index */
+ close (LOCAL->fd); /* finished with file */
+ LOCAL->fd = -1; /* no index now */
+ }
+}
+
+/* Set date for message
+ * Accepts: file name
+ * elt containing date
+ */
+
+void mx_setdate (char *file,MESSAGECACHE *elt)
+{
+ time_t tp[2];
+ tp[0] = time (0); /* atime is now */
+ tp[1] = mail_longdate (elt); /* modification time */
+ utime (file,tp); /* set the times */
+}
diff --git a/imap/src/osdep/unix/news.c b/imap/src/osdep/unix/news.c
new file mode 100644
index 00000000..4cf5bb70
--- /dev/null
+++ b/imap/src/osdep/unix/news.c
@@ -0,0 +1,738 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: News routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 4 September 1991
+ * Last Edited: 30 January 2007
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "misc.h"
+#include "newsrc.h"
+#include "fdstring.h"
+
+
+/* news_load_message() flags */
+
+#define NLM_HEADER 0x1 /* load message text */
+#define NLM_TEXT 0x2 /* load message text */
+
+/* NEWS I/O stream local data */
+
+typedef struct news_local {
+ unsigned int dirty : 1; /* disk copy of .newsrc needs updating */
+ char *dir; /* spool directory name */
+ char *name; /* local mailbox name */
+ unsigned char buf[CHUNKSIZE]; /* scratch buffer */
+ unsigned long cachedtexts; /* total size of all cached texts */
+} NEWSLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((NEWSLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *news_valid (char *name);
+DRIVER *news_isvalid (char *name,char *mbx);
+void *news_parameters (long function,void *value);
+void news_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void news_list (MAILSTREAM *stream,char *ref,char *pat);
+void news_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long news_canonicalize (char *ref,char *pat,char *pattern);
+long news_subscribe (MAILSTREAM *stream,char *mailbox);
+long news_unsubscribe (MAILSTREAM *stream,char *mailbox);
+long news_create (MAILSTREAM *stream,char *mailbox);
+long news_delete (MAILSTREAM *stream,char *mailbox);
+long news_rename (MAILSTREAM *stream,char *old,char *newname);
+MAILSTREAM *news_open (MAILSTREAM *stream);
+int news_select (struct direct *name);
+int news_numsort (const void *d1,const void *d2);
+void news_close (MAILSTREAM *stream,long options);
+void news_fast (MAILSTREAM *stream,char *sequence,long flags);
+void news_flags (MAILSTREAM *stream,char *sequence,long flags);
+void news_load_message (MAILSTREAM *stream,unsigned long msgno,long flags);
+char *news_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long news_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void news_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long news_ping (MAILSTREAM *stream);
+void news_check (MAILSTREAM *stream);
+long news_expunge (MAILSTREAM *stream,char *sequence,long options);
+long news_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long news_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+/* News routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER newsdriver = {
+ "news", /* driver name */
+ /* driver flags */
+ DR_NEWS|DR_READONLY|DR_NOFAST|DR_NAMESPACE|DR_NONEWMAIL|DR_DIRFMT,
+ (DRIVER *) NIL, /* next driver */
+ news_valid, /* mailbox is valid for us */
+ news_parameters, /* manipulate parameters */
+ news_scan, /* scan mailboxes */
+ news_list, /* find mailboxes */
+ news_lsub, /* find subscribed mailboxes */
+ news_subscribe, /* subscribe to mailbox */
+ news_unsubscribe, /* unsubscribe from mailbox */
+ news_create, /* create mailbox */
+ news_delete, /* delete mailbox */
+ news_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ news_open, /* open mailbox */
+ news_close, /* close mailbox */
+ news_fast, /* fetch message "fast" attributes */
+ news_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ news_header, /* fetch message header */
+ news_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ news_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ news_ping, /* ping mailbox to see if still alive */
+ news_check, /* check for new messages */
+ news_expunge, /* expunge deleted messages */
+ news_copy, /* copy messages to another mailbox */
+ news_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM newsproto = {&newsdriver};
+
+/* News validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *news_valid (char *name)
+{
+ int fd;
+ char *s,*t,*u;
+ struct stat sbuf;
+ if ((name[0] == '#') && (name[1] == 'n') && (name[2] == 'e') &&
+ (name[3] == 'w') && (name[4] == 's') && (name[5] == '.') &&
+ !strchr (name,'/') &&
+ !stat ((char *) mail_parameters (NIL,GET_NEWSSPOOL,NIL),&sbuf) &&
+ ((fd = open ((char *) mail_parameters (NIL,GET_NEWSACTIVE,NIL),O_RDONLY,
+ NIL)) >= 0)) {
+ fstat (fd,&sbuf); /* get size of active file */
+ /* slurp in active file */
+ read (fd,t = s = (char *) fs_get (sbuf.st_size+1),sbuf.st_size);
+ s[sbuf.st_size] = '\0'; /* tie off file */
+ close (fd); /* flush file */
+ while (*t && (u = strchr (t,' '))) {
+ *u++ = '\0'; /* tie off at end of name */
+ if (!strcmp (name+6,t)) {
+ fs_give ((void **) &s); /* flush data */
+ return &newsdriver;
+ }
+ t = 1 + strchr (u,'\n'); /* next line */
+ }
+ fs_give ((void **) &s); /* flush data */
+ }
+ return NIL; /* return status */
+}
+
+/* News manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *news_parameters (long function,void *value)
+{
+ return (function == GET_NEWSRC) ? env_parameters (function,value) : NIL;
+}
+
+
+/* News scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void news_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ char tmp[MAILTMPLEN];
+ if (news_canonicalize (ref,pat,tmp))
+ mm_log ("Scan not valid for news mailboxes",ERROR);
+}
+
+/* News find list of newsgroups
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void news_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ int fd;
+ int i;
+ char *s,*t,*u,*r,pattern[MAILTMPLEN],name[MAILTMPLEN];
+ struct stat sbuf;
+ if (!pat || !*pat) { /* empty pattern? */
+ if (news_canonicalize (ref,"*",pattern)) {
+ /* tie off name at root */
+ if (s = strchr (pattern,'.')) *++s = '\0';
+ else pattern[0] = '\0';
+ mm_list (stream,'.',pattern,LATT_NOSELECT);
+ }
+ }
+ else if (news_canonicalize (ref,pat,pattern) &&
+ !stat ((char *) mail_parameters (NIL,GET_NEWSSPOOL,NIL),&sbuf) &&
+ ((fd = open ((char *) mail_parameters (NIL,GET_NEWSACTIVE,NIL),
+ O_RDONLY,NIL)) >= 0)) {
+ fstat (fd,&sbuf); /* get file size and read data */
+ read (fd,s = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size);
+ close (fd); /* close file */
+ s[sbuf.st_size] = '\0'; /* tie off string */
+ strcpy (name,"#news."); /* write initial prefix */
+ i = strlen (pattern); /* length of pattern */
+ if (pattern[--i] != '%') i = 0;
+ if (t = strtok_r (s,"\n",&r)) do if (u = strchr (t,' ')) {
+ *u = '\0'; /* tie off at end of name */
+ strcpy (name + 6,t); /* make full form of name */
+ if (pmatch_full (name,pattern,'.')) mm_list (stream,'.',name,NIL);
+ else if (i && (u = strchr (name + i,'.'))) {
+ *u = '\0'; /* tie off at delimiter, see if matches */
+ if (pmatch_full (name,pattern,'.'))
+ mm_list (stream,'.',name,LATT_NOSELECT);
+ }
+ } while (t = strtok_r (NIL,"\n",&r));
+ fs_give ((void **) &s);
+ }
+}
+
+/* News find list of subscribed newsgroups
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void news_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ char pattern[MAILTMPLEN];
+ /* return data from newsrc */
+ if (news_canonicalize (ref,pat,pattern)) newsrc_lsub (stream,pattern);
+}
+
+
+/* News canonicalize newsgroup name
+ * Accepts: reference
+ * pattern
+ * returned single pattern
+ * Returns: T on success, NIL on failure
+ */
+
+long news_canonicalize (char *ref,char *pat,char *pattern)
+{
+ unsigned long i;
+ char *s;
+ if (ref && *ref) { /* have a reference */
+ strcpy (pattern,ref); /* copy reference to pattern */
+ /* # overrides mailbox field in reference */
+ if (*pat == '#') strcpy (pattern,pat);
+ /* pattern starts, reference ends, with . */
+ else if ((*pat == '.') && (pattern[strlen (pattern) - 1] == '.'))
+ strcat (pattern,pat + 1); /* append, omitting one of the period */
+ else strcat (pattern,pat); /* anything else is just appended */
+ }
+ else strcpy (pattern,pat); /* just have basic name */
+ if ((pattern[0] == '#') && (pattern[1] == 'n') && (pattern[2] == 'e') &&
+ (pattern[3] == 'w') && (pattern[4] == 's') && (pattern[5] == '.') &&
+ !strchr (pattern,'/')) { /* count wildcards */
+ for (i = 0, s = pattern; *s; *s++) if ((*s == '*') || (*s == '%')) ++i;
+ /* success if not too many */
+ if (i <= MAXWILDCARDS) return LONGT;
+ MM_LOG ("Excessive wildcards in LIST/LSUB",ERROR);
+ }
+ return NIL;
+}
+
+/* News subscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to add to subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long news_subscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return news_valid (mailbox) ? newsrc_update (stream,mailbox+6,':') : NIL;
+}
+
+
+/* NEWS unsubscribe to mailbox
+ * Accepts: mail stream
+ * mailbox to delete from subscription list
+ * Returns: T on success, NIL on failure
+ */
+
+long news_unsubscribe (MAILSTREAM *stream,char *mailbox)
+{
+ return news_valid (mailbox) ? newsrc_update (stream,mailbox+6,'!') : NIL;
+}
+
+/* News create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long news_create (MAILSTREAM *stream,char *mailbox)
+{
+ return NIL; /* never valid for News */
+}
+
+
+/* News delete mailbox
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long news_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return NIL; /* never valid for News */
+}
+
+
+/* News rename mailbox
+ * Accepts: mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long news_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ return NIL; /* never valid for News */
+}
+
+/* News open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *news_open (MAILSTREAM *stream)
+{
+ long i,nmsgs;
+ char *s,tmp[MAILTMPLEN];
+ struct direct **names = NIL;
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &newsproto;
+ if (stream->local) fatal ("news recycle stream");
+ /* build directory name */
+ sprintf (s = tmp,"%s/%s",(char *) mail_parameters (NIL,GET_NEWSSPOOL,NIL),
+ stream->mailbox + 6);
+ while (s = strchr (s,'.')) *s = '/';
+ /* scan directory */
+ if ((nmsgs = scandir (tmp,&names,news_select,news_numsort)) >= 0) {
+ mail_exists (stream,nmsgs); /* notify upper level that messages exist */
+ stream->local = fs_get (sizeof (NEWSLOCAL));
+ LOCAL->dirty = NIL; /* no update to .newsrc needed yet */
+ LOCAL->dir = cpystr (tmp); /* copy directory name for later */
+ LOCAL->name = cpystr (stream->mailbox + 6);
+ for (i = 0; i < nmsgs; ++i) {
+ stream->uid_last = mail_elt (stream,i+1)->private.uid =
+ atoi (names[i]->d_name);
+ fs_give ((void **) &names[i]);
+ }
+ s = (void *) names; /* stupid language */
+ fs_give ((void **) &s); /* free directory */
+ LOCAL->cachedtexts = 0; /* no cached texts */
+ stream->sequence++; /* bump sequence number */
+ stream->rdonly = stream->perm_deleted = T;
+ /* UIDs are always valid */
+ stream->uid_validity = 0xbeefface;
+ /* read .newsrc entries */
+ mail_recent (stream,newsrc_read (LOCAL->name,stream));
+ /* notify if empty newsgroup */
+ if (!(stream->nmsgs || stream->silent)) {
+ sprintf (tmp,"Newsgroup %s is empty",LOCAL->name);
+ mm_log (tmp,WARN);
+ }
+ }
+ else mm_log ("Unable to scan newsgroup spool directory",ERROR);
+ return LOCAL ? stream : NIL; /* if stream is alive, return to caller */
+}
+
+/* News file name selection test
+ * Accepts: candidate directory entry
+ * Returns: T to use file name, NIL to skip it
+ */
+
+int news_select (struct direct *name)
+{
+ char c;
+ char *s = name->d_name;
+ while (c = *s++) if (!isdigit (c)) return NIL;
+ return T;
+}
+
+
+/* News file name comparision
+ * Accepts: first candidate directory entry
+ * second candidate directory entry
+ * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
+ */
+
+int news_numsort (const void *d1,const void *d2)
+{
+ return atoi ((*(struct direct **) d1)->d_name) -
+ atoi ((*(struct direct **) d2)->d_name);
+}
+
+
+/* News close
+ * Accepts: MAIL stream
+ * option flags
+ */
+
+void news_close (MAILSTREAM *stream,long options)
+{
+ if (LOCAL) { /* only if a file is open */
+ news_check (stream); /* dump final checkpoint */
+ if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
+ if (LOCAL->name) fs_give ((void **) &LOCAL->name);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* News fetch fast information
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ */
+
+void news_fast (MAILSTREAM *stream,char *sequence,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned long i;
+ /* set up metadata for all messages */
+ if (stream && LOCAL && ((flags & FT_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence &&
+ !(elt->day && elt->rfc822_size)) news_load_message (stream,i,NIL);
+}
+
+
+/* News fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ */
+
+void news_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ unsigned long i;
+ if ((flags & FT_UID) ? /* validate all elts */
+ mail_uid_sequence (stream,sequence) : mail_sequence (stream,sequence))
+ for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->valid = T;
+}
+
+/* News load message into cache
+ * Accepts: MAIL stream
+ * message #
+ * option flags
+ */
+
+void news_load_message (MAILSTREAM *stream,unsigned long msgno,long flags)
+{
+ unsigned long i,j,nlseen;
+ int fd;
+ unsigned char c,*t;
+ struct stat sbuf;
+ MESSAGECACHE *elt;
+ FDDATA d;
+ STRING bs;
+ elt = mail_elt (stream,msgno);/* get elt */
+ /* build message file name */
+ sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
+ /* anything we need not currently cached? */
+ if ((!elt->day || !elt->rfc822_size ||
+ ((flags & NLM_HEADER) && !elt->private.msg.header.text.data) ||
+ ((flags & NLM_TEXT) && !elt->private.msg.text.text.data)) &&
+ ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0)) {
+ fstat (fd,&sbuf); /* get file metadata */
+ d.fd = fd; /* set up file descriptor */
+ d.pos = 0; /* start of file */
+ d.chunk = LOCAL->buf;
+ d.chunksize = CHUNKSIZE;
+ INIT (&bs,fd_string,&d,sbuf.st_size);
+ if (!elt->day) { /* set internaldate to file date */
+ struct tm *tm = gmtime (&sbuf.st_mtime);
+ elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
+ elt->year = tm->tm_year + 1900 - BASEYEAR;
+ elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
+ elt->seconds = tm->tm_sec;
+ elt->zhours = 0; elt->zminutes = 0;
+ }
+
+ if (!elt->rfc822_size) { /* know message size yet? */
+ for (i = 0, j = SIZE (&bs), nlseen = 0; j--; ) switch (SNX (&bs)) {
+ case '\015': /* unlikely carriage return */
+ if (!j || (CHR (&bs) != '\012')) {
+ i++; /* ugh, raw CR */
+ nlseen = NIL;
+ break;
+ }
+ SNX (&bs); /* eat the line feed, drop in */
+ case '\012': /* line feed? */
+ i += 2; /* count a CRLF */
+ /* header size known yet? */
+ if (!elt->private.msg.header.text.size && nlseen) {
+ /* note position in file */
+ elt->private.special.text.size = GETPOS (&bs);
+ /* and CRLF-adjusted size */
+ elt->private.msg.header.text.size = i;
+ }
+ nlseen = T; /* note newline seen */
+ break;
+ default: /* ordinary chararacter */
+ i++;
+ nlseen = NIL;
+ break;
+ }
+ SETPOS (&bs,0); /* restore old position */
+ elt->rfc822_size = i; /* note that we have size now */
+ /* header is entire message if no delimiter */
+ if (!elt->private.msg.header.text.size)
+ elt->private.msg.header.text.size = elt->rfc822_size;
+ /* text is remainder of message */
+ elt->private.msg.text.text.size =
+ elt->rfc822_size - elt->private.msg.header.text.size;
+ }
+
+ /* need to load cache with message data? */
+ if (((flags & NLM_HEADER) && !elt->private.msg.header.text.data) ||
+ ((flags & NLM_TEXT) && !elt->private.msg.text.text.data)) {
+ /* purge cache if too big */
+ if (LOCAL->cachedtexts > max (stream->nmsgs * 4096,2097152)) {
+ /* just can't keep that much */
+ mail_gc (stream,GC_TEXTS);
+ LOCAL->cachedtexts = 0;
+ }
+ if ((flags & NLM_HEADER) && !elt->private.msg.header.text.data) {
+ t = elt->private.msg.header.text.data =
+ (unsigned char *) fs_get (elt->private.msg.header.text.size + 1);
+ LOCAL->cachedtexts += elt->private.msg.header.text.size;
+ /* read in message header */
+ for (i = 0; i <= elt->private.msg.header.text.size; i++)
+ switch (c = SNX (&bs)) {
+ case '\015': /* unlikely carriage return */
+ *t++ = c;
+ if ((CHR (&bs) == '\012')) *t++ = SNX (&bs);
+ break;
+ case '\012': /* line feed? */
+ *t++ = '\015';
+ default:
+ *t++ = c;
+ break;
+ }
+ *t = '\0'; /* tie off string */
+ }
+ if ((flags & NLM_TEXT) && !elt->private.msg.text.text.data) {
+ t = elt->private.msg.text.text.data =
+ (unsigned char *) fs_get (elt->private.msg.text.text.size + 1);
+ SETPOS (&bs,elt->private.msg.header.text.size);
+ LOCAL->cachedtexts += elt->private.msg.text.text.size;
+ /* read in message text */
+ for (i = 0; i <= elt->private.msg.text.text.size; i++)
+ switch (c = SNX (&bs)) {
+ case '\015': /* unlikely carriage return */
+ *t++ = c;
+ if ((CHR (&bs) == '\012')) *t++ = SNX (&bs);
+ break;
+ case '\012': /* line feed? */
+ *t++ = '\015';
+ default:
+ *t++ = c;
+ break;
+ }
+ *t = '\0'; /* tie off string */
+ }
+ }
+ close (fd); /* flush message file */
+ }
+}
+
+/* News fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *news_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ MESSAGECACHE *elt;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get elt */
+ if (!elt->private.msg.header.text.data)
+ news_load_message (stream,msgno,NLM_HEADER);
+ *length = elt->private.msg.header.text.size;
+ return (char *) elt->private.msg.header.text.data;
+}
+
+
+/* News fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL on failure
+ */
+
+long news_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);/* get elt */
+ /* snarf message if don't have it yet */
+ if (!elt->private.msg.text.text.data) {
+ news_load_message (stream,msgno,NLM_TEXT);
+ if (!elt->private.msg.text.text.data) return NIL;
+ }
+ if (!(flags & FT_PEEK)) { /* mark as seen */
+ mail_elt (stream,msgno)->seen = T;
+ mm_flags (stream,msgno);
+ }
+ INIT (bs,mail_string,elt->private.msg.text.text.data,
+ elt->private.msg.text.text.size);
+ return T;
+}
+
+/* News per-message modify flag
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void news_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ if (!LOCAL->dirty) { /* only bother checking if not dirty yet */
+ if (elt->valid) { /* if done, see if deleted changed */
+ if (elt->sequence != elt->deleted) LOCAL->dirty = T;
+ elt->sequence = T; /* leave the sequence set */
+ }
+ /* note current setting of deleted flag */
+ else elt->sequence = elt->deleted;
+ }
+}
+
+
+/* News ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long news_ping (MAILSTREAM *stream)
+{
+ return T; /* always alive */
+}
+
+
+/* News check mailbox
+ * Accepts: MAIL stream
+ */
+
+void news_check (MAILSTREAM *stream)
+{
+ /* never do if no updates */
+ if (LOCAL->dirty) newsrc_write (LOCAL->name,stream);
+ LOCAL->dirty = NIL;
+}
+
+
+/* News expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T if success, NIL if failure
+ */
+
+long news_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
+ return LONGT;
+}
+
+/* News copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * option flags
+ * Returns: T if copy successful, else NIL
+ */
+
+long news_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ mm_log ("Copy not valid for News",ERROR);
+ return NIL;
+}
+
+
+/* News append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback function
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long news_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ mm_log ("Append not valid for news",ERROR);
+ return NIL;
+}
diff --git a/imap/src/osdep/unix/nl_unix.c b/imap/src/osdep/unix/nl_unix.c
new file mode 100644
index 00000000..b2d5616a
--- /dev/null
+++ b/imap/src/osdep/unix/nl_unix.c
@@ -0,0 +1,92 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX/VMS newline routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Copy string with CRLF newlines
+ * Accepts: destination string
+ * pointer to size of destination string buffer
+ * source string
+ * length of source string
+ * Returns: length of copied string
+ */
+
+unsigned long strcrlfcpy (unsigned char **dst,unsigned long *dstl,
+ unsigned char *src,unsigned long srcl)
+{
+ long i = srcl * 2,j;
+ unsigned char c,*d = src;
+ if (*dst) { /* candidate destination provided? */
+ /* count NLs if doesn't fit worst-case */
+ if (i > *dstl) for (i = j = srcl; j; --j) if (*d++ == '\012') i++;
+ /* still too small, must reset destination */
+ if (i > *dstl) fs_give ((void **) dst);
+ }
+ /* make a new buffer if needed */
+ if (!*dst) *dst = (char *) fs_get ((*dstl = i) + 1);
+ d = *dst; /* destination string */
+ if (srcl) do { /* main copy loop */
+ if ((c = *src++) < '\016') {
+ /* prepend CR to LF */
+ if (c == '\012') *d++ = '\015';
+ /* unlikely CR */
+ else if ((c == '\015') && (srcl > 1) && (*src == '\012')) {
+ *d++ = c; /* copy the CR */
+ c = *src++; /* grab the LF */
+ --srcl; /* adjust the count */
+ }
+ }
+ *d++ = c; /* copy character */
+ } while (--srcl);
+ *d = '\0'; /* tie off destination */
+ return d - *dst; /* return length */
+}
+
+/* Length of string after strcrlfcpy applied
+ * Accepts: source string
+ * Returns: length of string
+ */
+
+unsigned long strcrlflen (STRING *s)
+{
+ unsigned long pos = GETPOS (s);
+ unsigned long i = SIZE (s);
+ unsigned long j = i;
+ while (j--) switch (SNX (s)) {/* search for newlines */
+ case '\015': /* unlikely carriage return */
+ if (j && (CHR (s) == '\012')) {
+ SNX (s); /* eat the line feed */
+ j--;
+ }
+ break;
+ case '\012': /* line feed? */
+ i++;
+ default: /* ordinary chararacter */
+ break;
+ }
+ SETPOS (s,pos); /* restore old position */
+ return i;
+}
diff --git a/imap/src/osdep/unix/opendir.c b/imap/src/osdep/unix/opendir.c
new file mode 100644
index 00000000..445fd148
--- /dev/null
+++ b/imap/src/osdep/unix/opendir.c
@@ -0,0 +1,79 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Read directories
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 16 December 1993
+ * Last Edited: 30 August 2006
+ */
+
+/* Emulator for BSD opendir() call
+ * Accepts: directory name
+ * Returns: directory structure pointer
+ */
+
+DIR *opendir (char *name)
+{
+ DIR *d = NIL;
+ struct stat sbuf;
+ int fd = open (name,O_RDONLY,NIL);
+ errno = ENOTDIR; /* default error is bogus directory */
+ if ((fd >= 0) && !(fstat (fd,&sbuf)) && ((sbuf.st_mode&S_IFMT) == S_IFDIR)) {
+ d = (DIR *) fs_get (sizeof (DIR));
+ /* initialize structure */
+ d->dd_loc = 0;
+ read (fd,d->dd_buf = (char *) fs_get (sbuf.st_size),
+ d->dd_size = sbuf.st_size);
+ }
+ else if (d) fs_give ((void **) &d);
+ if (fd >= 0) close (fd);
+ return d;
+}
+
+
+/* Emulator for BSD closedir() call
+ * Accepts: directory structure pointer
+ */
+
+int closedir (DIR *d)
+{
+ /* free storage */
+ fs_give ((void **) &(d->dd_buf));
+ fs_give ((void **) &d);
+ return NIL; /* return */
+}
+
+
+/* Emulator for BSD readdir() call
+ * Accepts: directory structure pointer
+ */
+
+struct direct *readdir (DIR *d)
+{
+ /* loop through directory */
+ while (d->dd_loc < d->dd_size) {
+ struct direct *dp = (struct direct *) (d->dd_buf + d->dd_loc);
+ d->dd_loc += sizeof (struct direct);
+ if (dp->d_ino) return dp; /* if have a good entry return it */
+ }
+ return NIL; /* all done */
+}
diff --git a/imap/src/osdep/unix/os_a32.c b/imap/src/osdep/unix/os_a32.c
new file mode 100644
index 00000000..513e92c7
--- /dev/null
+++ b/imap/src/osdep/unix/os_a32.c
@@ -0,0 +1,60 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- AIX 3.2 on RS 6000
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+#include <sys/select.h>
+
+char *crypt (char *key,char *salt);
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_sv4.c"
+#include "flocksim.c"
+#include "utime.c"
diff --git a/imap/src/osdep/unix/os_a32.h b/imap/src/osdep/unix/os_a32.h
new file mode 100644
index 00000000..40dd6fd4
--- /dev/null
+++ b/imap/src/osdep/unix/os_a32.h
@@ -0,0 +1,50 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- AIX on RS6000
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h> /* for struct tm */
+#include <dirent.h>
+#include <fcntl.h>
+#include <utime.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_a41.c b/imap/src/osdep/unix/os_a41.c
new file mode 100644
index 00000000..ecaff582
--- /dev/null
+++ b/imap/src/osdep/unix/os_a41.c
@@ -0,0 +1,61 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- AIX 4.1 on RS 6000
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+#include <sys/select.h>
+#include <stddef.h> /* needed for authenticate() */
+
+int authenticate (char *UserName,char *Response,int *Reenter,char **Message);
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_sv4.c"
+#include "flocksim.c"
+#include "utime.c"
diff --git a/imap/src/osdep/unix/os_a41.h b/imap/src/osdep/unix/os_a41.h
new file mode 100644
index 00000000..40dd6fd4
--- /dev/null
+++ b/imap/src/osdep/unix/os_a41.h
@@ -0,0 +1,50 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- AIX on RS6000
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h> /* for struct tm */
+#include <dirent.h>
+#include <fcntl.h>
+#include <utime.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_a52.c b/imap/src/osdep/unix/os_a52.c
new file mode 100644
index 00000000..fed6af78
--- /dev/null
+++ b/imap/src/osdep/unix/os_a52.c
@@ -0,0 +1,63 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- AIX 4.1 on RS 6000
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+#include <sys/select.h>
+#include <stddef.h> /* needed for authenticate() */
+
+int authenticate (char *UserName,char *Response,int *Reenter,char **Message);
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_sv4.c"
+#include "flocksim.c"
+#undef setpgrp
+#include "setpgrp.c"
+#include "utime.c"
diff --git a/imap/src/osdep/unix/os_a52.h b/imap/src/osdep/unix/os_a52.h
new file mode 100644
index 00000000..a8261746
--- /dev/null
+++ b/imap/src/osdep/unix/os_a52.h
@@ -0,0 +1,53 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- AIX on RS6000
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h> /* for struct tm */
+#include <dirent.h>
+#include <fcntl.h>
+#include <utime.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+#define setpgrp(a,b) Setpgrp(a,b)
+int Setpgrp (int pid,int gid);
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_aix.c b/imap/src/osdep/unix/os_aix.c
new file mode 100644
index 00000000..c38e5cf9
--- /dev/null
+++ b/imap/src/osdep/unix/os_aix.c
@@ -0,0 +1,64 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- AIX version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+char *crypt (char *key,char *salt);
+
+extern long timezone;
+extern int daylight;
+extern char *tzname[2];
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "memmove.c"
+#include "strerror.c"
+#include "tz_sv4.c"
diff --git a/imap/src/osdep/unix/os_aix.h b/imap/src/osdep/unix/os_aix.h
new file mode 100644
index 00000000..6c2d4340
--- /dev/null
+++ b/imap/src/osdep/unix/os_aix.h
@@ -0,0 +1,47 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- AIX version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+#define direct dirent
+
+char *strerror (int n);
+void *memmove (void *s,void *ct,size_t n);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_aos.c b/imap/src/osdep/unix/os_aos.c
new file mode 100644
index 00000000..a916f47b
--- /dev/null
+++ b/imap/src/osdep/unix/os_aos.c
@@ -0,0 +1,63 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- 4.3BSD version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ *
+ * Date: 11 May 1989
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+#define toint(c) ((c)-'0')
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_wait.c"
+#include "memmove.c"
+#include "strerror.c"
+#include "strstr.c"
+#include "strtoul.c"
+#include "tz_bsd.c"
diff --git a/imap/src/osdep/unix/os_aos.h b/imap/src/osdep/unix/os_aos.h
new file mode 100644
index 00000000..b604a475
--- /dev/null
+++ b/imap/src/osdep/unix/os_aos.h
@@ -0,0 +1,50 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- AOS version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+char *getenv (char *name);
+char *strstr (char *cs,char *ct);
+char *strerror (int n);
+void *memmove (void *s,void *ct,size_t n);
+unsigned long strtoul (char *s,char **endp,int base);
+void *malloc (size_t byteSize);
+void free (void *ptr);
+void *realloc (void *oldptr,size_t newsize);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_art.c b/imap/src/osdep/unix/os_art.c
new file mode 100644
index 00000000..8fb78028
--- /dev/null
+++ b/imap/src/osdep/unix/os_art.c
@@ -0,0 +1,86 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- AIX/RT version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 1992
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+extern int errno;
+#include <pwd.h>
+#include <sys/socket.h>
+#include <time.h>
+#define KERNEL
+#include <sys/time.h>
+#undef KERNEL
+#include "misc.h"
+
+#define DIR_SIZE(d) sizeof (DIR)
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+#define toint(c) ((c)-'0')
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+
+#define NBBY 8 /* number of bits in a byte */
+#define FD_SETSIZE 256
+
+typedef long fd_mask;
+#define NFDBITS (sizeof(fd_mask) * NBBY)
+ /* bits per mask */
+#define howmany(x, y) (((x)+((y)-1))/(y))
+
+typedef struct fd_set {
+ fd_mask fds_bits[howmany(FD_SETSIZE, NFDBITS)];
+} fd_set;
+
+#define FD_SET(n, p) ((p)->fds_bits[(n)/NFDBITS] |= \
+ (1 << ((n) % NFDBITS)))
+#define FD_CLR(n, p) ((p)->fds_bits[(n)/NFDBITS] &= \
+ ~(1 << ((n) % NFDBITS)))
+#define FD_ISSET(n, p) ((p)->fds_bits[(n)/NFDBITS] & \
+ (1 << ((n) % NFDBITS)))
+#define FD_ZERO(p) bzero((char *)(p), sizeof(*(p)))
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_wait.c"
+#include "flocksim.c"
+#include "memmove2.c"
+#include "strerror.c"
+#include "tz_sv4.c"
diff --git a/imap/src/osdep/unix/os_art.h b/imap/src/osdep/unix/os_art.h
new file mode 100644
index 00000000..9bfd6f86
--- /dev/null
+++ b/imap/src/osdep/unix/os_art.h
@@ -0,0 +1,81 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- AIX/RT version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 1992
+ * Last Edited: 15 September 2006
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <memory.h>
+#include <sys/types.h>
+#define direct dirent
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+/* Different names between BSD and SVR4 */
+
+#define L_SET SEEK_SET
+#define L_INCR SEEK_CUR
+#define L_XTND SEEK_END
+
+#define random lrand48
+
+#define SIGSTOP SIGQUIT
+
+
+/* For setitimer() emulation */
+
+#define ITIMER_REAL 0
+
+struct passwd *getpwent (void);
+struct passwd *getpwuid (int uid);
+struct passwd *getpwnam (char *name);
+char *getenv (char *name);
+long gethostid (void);
+void *memmove (void *s,void *ct,size_t n);
+char *strstr (char *cs,char *ct);
+char *strerror (int n);
+unsigned long strtoul (char *s,char **endp,int base);
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (void *d1,void *d2);
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar);
+int alphasort (void *d1,void *d2);
+void *malloc (size_t byteSize);
+void free (void *ptr);
+void *realloc (void *oldptr,size_t newsize);
+int openlog (ident,logopt,facility);
+int syslog (priority,message,parameters ...);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_asv.c b/imap/src/osdep/unix/os_asv.c
new file mode 100644
index 00000000..bf943b3e
--- /dev/null
+++ b/imap/src/osdep/unix/os_asv.c
@@ -0,0 +1,70 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Altos System V version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 1992
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/tiuser.h>
+#include <sys/stropts.h>
+#include <sys/poll.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+#include <pwd.h>
+#include <sys/socket.h>
+#include "misc.h"
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+#define toint(c) ((c)-'0')
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+
+#define DIR_SIZE(d) d->d_reclen
+
+#define pid_t short /* may not be known on all ASV systems */
+
+#include "strstr.c"
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "strerror.c"
+#include "flocksim.c"
+#include "scandir.c"
+#include "strtoul.c"
+#include "tz_sv4.c"
+#include "gethstid.c"
+#include "memmove.c"
+#include "fsync.c"
diff --git a/imap/src/osdep/unix/os_asv.h b/imap/src/osdep/unix/os_asv.h
new file mode 100644
index 00000000..f92f210b
--- /dev/null
+++ b/imap/src/osdep/unix/os_asv.h
@@ -0,0 +1,76 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Altos System V version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 1992
+ * Last Edited: 15 September 2006
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/time.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+/* Different names, equivalent things in BSD and SysV */
+
+#define L_SET SEEK_SET
+#define L_INCR SEEK_CUR
+#define L_XTND SEEK_END
+
+#define direct dirent
+
+#define ftruncate chsize
+#define random lrand48
+
+
+struct passwd *getpwent (void);
+struct passwd *getpwuid (int uid);
+struct passwd *getpwnam (char *name);
+char *getenv (char *name);
+long gethostid (void);
+void *memmove (void *s,void *ct,size_t n);
+char *strstr (char *cs,char *ct);
+char *strerror (int n);
+unsigned long strtoul (char *s,char **endp,int base);
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (void *d1,void *d2);
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar);
+int alphasort (void *d1,void *d2);
+void *malloc (size_t byteSize);
+void free (void *ptr);
+void *realloc (void *oldptr,size_t newsize);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_aux.c b/imap/src/osdep/unix/os_aux.c
new file mode 100644
index 00000000..ec5f666b
--- /dev/null
+++ b/imap/src/osdep/unix/os_aux.c
@@ -0,0 +1,62 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- A/UX version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+#include <pwd.h>
+#include "misc.h"
+
+
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+#define toint(c) ((c)-'0')
+
+extern char *sys_errlist[];
+extern int sys_nerr;
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_wait.c"
+#include "flocksim.c"
+#include "strerror.c"
+#include "strtoul.c"
+#include "strpbrk.c" /* the A/UX version is bogus! */
+#include "memmove.c"
+#include "tz_sv4.c"
diff --git a/imap/src/osdep/unix/os_aux.h b/imap/src/osdep/unix/os_aux.h
new file mode 100644
index 00000000..53ec6cc5
--- /dev/null
+++ b/imap/src/osdep/unix/os_aux.h
@@ -0,0 +1,51 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- A/UX version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <time.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include <syslog.h>
+
+
+extern int errno;
+
+char *strerror (int n);
+unsigned long strtoul (char *s,char **endp,int base);
+void *memmove (void *s,void *ct,size_t n);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_bsd.c b/imap/src/osdep/unix/os_bsd.c
new file mode 100644
index 00000000..a916f47b
--- /dev/null
+++ b/imap/src/osdep/unix/os_bsd.c
@@ -0,0 +1,63 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- 4.3BSD version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ *
+ * Date: 11 May 1989
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+#define toint(c) ((c)-'0')
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_wait.c"
+#include "memmove.c"
+#include "strerror.c"
+#include "strstr.c"
+#include "strtoul.c"
+#include "tz_bsd.c"
diff --git a/imap/src/osdep/unix/os_bsd.h b/imap/src/osdep/unix/os_bsd.h
new file mode 100644
index 00000000..851434af
--- /dev/null
+++ b/imap/src/osdep/unix/os_bsd.h
@@ -0,0 +1,51 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- 4.3BSD version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <machine/endian.h> /* needed for htons() prototypes */
+
+
+char *getenv (char *name);
+char *strstr (char *cs,char *ct);
+char *strerror (int n);
+void *memmove (void *s,void *ct,size_t n);
+unsigned long strtoul (char *s,char **endp,int base);
+void *malloc (size_t byteSize);
+void free (void *ptr);
+void *realloc (void *oldptr,size_t newsize);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_bsf.c b/imap/src/osdep/unix/os_bsf.c
new file mode 100644
index 00000000..afe26e23
--- /dev/null
+++ b/imap/src/osdep/unix/os_bsf.c
@@ -0,0 +1,54 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- BSDI BSD/386 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "getspnam.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_bsd.c"
diff --git a/imap/src/osdep/unix/os_bsf.h b/imap/src/osdep/unix/os_bsf.h
new file mode 100644
index 00000000..af18d567
--- /dev/null
+++ b/imap/src/osdep/unix/os_bsf.h
@@ -0,0 +1,46 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- FreeBSD version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 5 March 1993
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+#define direct dirent
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_bsi.c b/imap/src/osdep/unix/os_bsi.c
new file mode 100644
index 00000000..afe26e23
--- /dev/null
+++ b/imap/src/osdep/unix/os_bsi.c
@@ -0,0 +1,54 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- BSDI BSD/386 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "getspnam.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_bsd.c"
diff --git a/imap/src/osdep/unix/os_bsi.h b/imap/src/osdep/unix/os_bsi.h
new file mode 100644
index 00000000..15f371a3
--- /dev/null
+++ b/imap/src/osdep/unix/os_bsi.h
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- BSDI BSD/386 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 5 March 1993
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_cvx.c b/imap/src/osdep/unix/os_cvx.c
new file mode 100644
index 00000000..10f01904
--- /dev/null
+++ b/imap/src/osdep/unix/os_cvx.c
@@ -0,0 +1,57 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Convex version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ *
+ * Date: 11 May 1989
+ * Last Edited: 16 August 2007
+ */
+
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+#define toint(c) ((c)-'0')
+
+#include <stdio.h>
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_wait.c"
+#include "tz_nul.c"
diff --git a/imap/src/osdep/unix/os_cvx.h b/imap/src/osdep/unix/os_cvx.h
new file mode 100644
index 00000000..cdb0e92f
--- /dev/null
+++ b/imap/src/osdep/unix/os_cvx.h
@@ -0,0 +1,45 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Convex version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <sys/timeb.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+void *malloc (size_t byteSize);
+void *realloc (void *oldptr,size_t newsize);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_cyg.c b/imap/src/osdep/unix/os_cyg.c
new file mode 100644
index 00000000..c9cd2f70
--- /dev/null
+++ b/imap/src/osdep/unix/os_cyg.c
@@ -0,0 +1,71 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Cygwin version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include <crypt.h>
+#include "misc.h"
+
+
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+#define toint(c) ((c)-'0')
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_wait.c"
+#include "tz_nul.c"
+#include "flockcyg.c"
+#include "gethstid.c"
+
+
+/* Emulator for geteuid() call
+ * Returns: effective UID
+ */
+
+#undef geteuid
+
+uid_t Geteuid (void)
+{
+ uid_t ret = geteuid ();
+ return (ret == SYSTEMUID) ? 0 : ret;
+}
diff --git a/imap/src/osdep/unix/os_cyg.h b/imap/src/osdep/unix/os_cyg.h
new file mode 100644
index 00000000..061db332
--- /dev/null
+++ b/imap/src/osdep/unix/os_cyg.h
@@ -0,0 +1,71 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Cygwin version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <time.h>
+#include <sys/time.h>
+
+#define direct dirent
+
+
+#define CYGKLUDGEOFFSET 1 /* don't write 1st byte of shared-lock files */
+
+/* Cygwin gets this wrong */
+
+#define setpgrp setpgid
+
+#define SYSTEMUID 18 /* Cygwin returns this for SYSTEM */
+#define geteuid Geteuid
+uid_t Geteuid (void);
+
+/* Now Cygwin has reportedly joined this madness. Use ifndef in case it shares
+ the SVR4 <sys/file.h> silliness too */
+#ifndef L_SET
+#define L_SET SEEK_SET
+#endif
+#ifndef L_INCR
+#define L_INCR SEEK_CUR
+#endif
+#ifndef L_XTND
+#define L_XTND SEEK_END
+#endif
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flockcyg.h"
diff --git a/imap/src/osdep/unix/os_d-g.c b/imap/src/osdep/unix/os_d-g.c
new file mode 100644
index 00000000..b34662f7
--- /dev/null
+++ b/imap/src/osdep/unix/os_d-g.c
@@ -0,0 +1,54 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- D-G version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_sv4.c"
+#include "flocksim.c"
+#include "utime.c"
diff --git a/imap/src/osdep/unix/os_d-g.h b/imap/src/osdep/unix/os_d-g.h
new file mode 100644
index 00000000..3d308038
--- /dev/null
+++ b/imap/src/osdep/unix/os_d-g.h
@@ -0,0 +1,56 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- D-G version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/dir.h>
+#include <time.h> /* for struct tm */
+#include <fcntl.h>
+#define _USEC_UTIME_FLAVOR /* break it for compatibility with */
+#include <utime.h> /* the incompatible past */
+#include <syslog.h>
+#include <sys/file.h>
+
+
+/* D-G gets this wrong */
+
+#define setpgrp setpgrp2
+
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_do4.c b/imap/src/osdep/unix/os_do4.c
new file mode 100644
index 00000000..186fd94e
--- /dev/null
+++ b/imap/src/osdep/unix/os_do4.c
@@ -0,0 +1,58 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Apollo Domain/OS sr10.4
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ *
+ * Date: 11 May 1989
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+#define toint(c) ((c)-'0')
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_sv4.c"
diff --git a/imap/src/osdep/unix/os_do4.h b/imap/src/osdep/unix/os_do4.h
new file mode 100644
index 00000000..196061c9
--- /dev/null
+++ b/imap/src/osdep/unix/os_do4.h
@@ -0,0 +1,47 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Apollo Domain/OS sr10.4
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+#ifndef htons
+#include <netinet/in.h> /* needed for htons() prototypes */
+#endif
+
+extern int daylight; /* local timzone uses daylight savings time */
+extern long altzone; /* seconds west of UTC during daylight time */
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_drs.c b/imap/src/osdep/unix/os_drs.c
new file mode 100644
index 00000000..74386af9
--- /dev/null
+++ b/imap/src/osdep/unix/os_drs.c
@@ -0,0 +1,57 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- ICL DRS/NX
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+char *crypt (char *key,char *salt);
+
+#define DIR_SIZE(d) d->d_reclen
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_sv4.c"
+#include "flocksim.c"
+#include "scandir.c"
diff --git a/imap/src/osdep/unix/os_drs.h b/imap/src/osdep/unix/os_drs.h
new file mode 100644
index 00000000..d90755ee
--- /dev/null
+++ b/imap/src/osdep/unix/os_drs.h
@@ -0,0 +1,59 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- ICL DRS/NX version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 15 September 2006
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <time.h> /* for struct tm */
+#include <dirent.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+#define random rand
+#define direct dirent
+
+
+long gethostid (void);
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (void *d1,void *d2);
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar);
+int alphasort (void *d1,void *d2);
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_dyn.c b/imap/src/osdep/unix/os_dyn.c
new file mode 100644
index 00000000..98a1a548
--- /dev/null
+++ b/imap/src/osdep/unix/os_dyn.c
@@ -0,0 +1,64 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Dynix version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ *
+ * Date: 11 May 1989
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+#define toint(c) ((c)-'0')
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_wait.c"
+#include "memmove.c"
+#include "strerror.c"
+#include "strpbrk.c"
+#include "strstr.c"
+#include "strtoul.c"
+#include "strtok.c"
+#include "tz_nul.c"
diff --git a/imap/src/osdep/unix/os_dyn.h b/imap/src/osdep/unix/os_dyn.h
new file mode 100644
index 00000000..e5426498
--- /dev/null
+++ b/imap/src/osdep/unix/os_dyn.h
@@ -0,0 +1,61 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Dynix version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 January 2007
+ */
+
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+typedef unsigned long size_t;
+
+char *strtok (char *s,char *ct);
+char *strtok_r (char *s,char *ct,char **r);
+char *strstr (char *cs,char *ct);
+char *strpbrk (char *cs,char *ct);
+char *strerror (int n);
+void *memmove (void *s,void *ct,size_t n);
+void *memset (void *s,int c,size_t n);
+unsigned long strtoul (char *s,char **endp,int base);
+void *malloc (size_t byteSize);
+void free (void *ptr);
+void *realloc (void *oldptr,size_t newsize);
+
+int errno;
+
+#define memcpy memmove
+#define strchr index
+#define strrchr rindex
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_hpp.c b/imap/src/osdep/unix/os_hpp.c
new file mode 100644
index 00000000..39794973
--- /dev/null
+++ b/imap/src/osdep/unix/os_hpp.c
@@ -0,0 +1,77 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- HP/UX version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 16 August 2007
+ */
+
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+#define toint(c) ((c)-'0')
+
+#include <stdio.h>
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+extern char *sys_errlist[];
+extern int sys_nerr;
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "flocksim.c"
+#include "tz_sv4.c"
+#undef setpgrp
+#include "setpgrp.c"
+#include "utime.c"
+
+/* Emulator for BSD gethostid() call
+ * Returns: a unique identifier for the system.
+ * Even though HP/UX has an undocumented gethostid() system call,
+ * it does not work (at least for non-privileged users).
+ */
+
+long gethostid (void)
+{
+ struct utsname udata;
+ return (uname (&udata)) ? 0xfeedface : atol (udata.__idnumber);
+}
diff --git a/imap/src/osdep/unix/os_hpp.h b/imap/src/osdep/unix/os_hpp.h
new file mode 100644
index 00000000..d51dba53
--- /dev/null
+++ b/imap/src/osdep/unix/os_hpp.h
@@ -0,0 +1,63 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- HP/UX version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 20 December 2006
+ */
+
+#include <string.h>
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <utime.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+#define direct dirent
+#define random lrand48
+
+
+/* Many versions of SysV get this wrong */
+
+#define setpgrp(a,b) Setpgrp(a,b)
+int Setpgrp (int pid,int gid);
+
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+long gethostid (void);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_isc.c b/imap/src/osdep/unix/os_isc.c
new file mode 100644
index 00000000..1269e69b
--- /dev/null
+++ b/imap/src/osdep/unix/os_isc.c
@@ -0,0 +1,68 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- ISC version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 1992
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/tiuser.h>
+#include <sys/stropts.h>
+#include <sys/poll.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+#include <net/errno.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <sys/socket.h>
+#include "misc.h"
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+#define toint(c) ((c)-'0')
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+
+#define DIR_SIZE(d) d->d_reclen
+
+#define pid_t short /* may not be known on all ISC systems */
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "strerror.c"
+#include "flocksim.c"
+#include "scandir.c"
+#include "tz_sv4.c"
+#include "fsync.c"
diff --git a/imap/src/osdep/unix/os_isc.h b/imap/src/osdep/unix/os_isc.h
new file mode 100644
index 00000000..1e7c22eb
--- /dev/null
+++ b/imap/src/osdep/unix/os_isc.h
@@ -0,0 +1,70 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- ISC version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 1992
+ * Last Edited: 15 September 2006
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/bsdtypes.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+/* Different names, equivalent things in BSD and SysV */
+
+/* L_SET is defined for some strange reason in <sys/file.h> on SVR4. */
+#ifndef L_SET
+#define L_SET SEEK_SET
+#endif
+#define L_INCR SEEK_CUR
+#define L_XTND SEEK_END
+
+#define direct dirent
+
+#define ftruncate chsize
+#define random lrand48
+
+long gethostid (void);
+void *memmove (void *s,void *ct,size_t n);
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (void *d1,void *d2);
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar);
+int alphasort (void *d1,void *d2);
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_lnx.c b/imap/src/osdep/unix/os_lnx.c
new file mode 100644
index 00000000..03fd17d9
--- /dev/null
+++ b/imap/src/osdep/unix/os_lnx.c
@@ -0,0 +1,54 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- old Linux version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1993
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_sv4.c"
+#include "flocklnx.c"
diff --git a/imap/src/osdep/unix/os_lnx.h b/imap/src/osdep/unix/os_lnx.h
new file mode 100644
index 00000000..b5f39ffa
--- /dev/null
+++ b/imap/src/osdep/unix/os_lnx.h
@@ -0,0 +1,67 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Linux version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 September 1993
+ * Last Edited: 30 August 2006
+ */
+
+/*
+ *** These lines are claimed to be necessary to build on Debian Linux on an
+ *** Alpha.
+ */
+
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE 1
+#endif /* _XOPEN_SOURCE */
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE 1
+#endif /* _BSD_SOURCE */
+
+/* end Debian Linux on Alpha strangeness */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h> /* for struct tm */
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+/* Linux gets this wrong */
+
+#define setpgrp setpgid
+
+#define direct dirent
+
+#define flock safe_flock
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_lyn.c b/imap/src/osdep/unix/os_lyn.c
new file mode 100644
index 00000000..49871251
--- /dev/null
+++ b/imap/src/osdep/unix/os_lyn.c
@@ -0,0 +1,54 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- LynxOS version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+char *crypt (char *key,char *salt);
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_nul.c"
diff --git a/imap/src/osdep/unix/os_lyn.h b/imap/src/osdep/unix/os_lyn.h
new file mode 100644
index 00000000..bba4a3d6
--- /dev/null
+++ b/imap/src/osdep/unix/os_lyn.h
@@ -0,0 +1,44 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- LynxOS version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 5 March 1993
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+#define gethostid clock
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_mct.c b/imap/src/osdep/unix/os_mct.c
new file mode 100644
index 00000000..cb62de50
--- /dev/null
+++ b/imap/src/osdep/unix/os_mct.c
@@ -0,0 +1,52 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- MachTen version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 7 December 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_wait4.c"
+#include "tz_bsd.c"
diff --git a/imap/src/osdep/unix/os_mct.h b/imap/src/osdep/unix/os_mct.h
new file mode 100644
index 00000000..f4b98d5e
--- /dev/null
+++ b/imap/src/osdep/unix/os_mct.h
@@ -0,0 +1,44 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- MachTen version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 7 December 2006
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+#define unix 1
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_mnt.c b/imap/src/osdep/unix/os_mnt.c
new file mode 100644
index 00000000..f19a487b
--- /dev/null
+++ b/imap/src/osdep/unix/os_mnt.c
@@ -0,0 +1,53 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Mint version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_nul.c"
diff --git a/imap/src/osdep/unix/os_mnt.h b/imap/src/osdep/unix/os_mnt.h
new file mode 100644
index 00000000..4519f8b7
--- /dev/null
+++ b/imap/src/osdep/unix/os_mnt.h
@@ -0,0 +1,51 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Mint version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 September 1993
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <time.h>
+#include <portlib.h>
+
+#define EAGAIN EWOULDBLOCK
+#define FNDELAY O_NDELAY
+
+/* MiNT gets this wrong */
+
+#define setpgrp setpgid
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_nto.c b/imap/src/osdep/unix/os_nto.c
new file mode 100644
index 00000000..36c45b42
--- /dev/null
+++ b/imap/src/osdep/unix/os_nto.c
@@ -0,0 +1,76 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- QNX Neutrino RTP version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1993
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include <shadow.h>
+#include <sys/select.h>
+#include "misc.h"
+
+#define DIR_SIZE(d) d->d_reclen
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_wait.c"
+#include "tz_sv4.c"
+#include "gethstid.c"
+#include "flocksim.c"
+#include "utime.c"
+
+/* QNX local readdir()
+ * Accepts: directory structure
+ * Returns: direct struct or NIL if failed
+ */
+
+#undef readdir
+
+struct direct *Readdir (DIR *dirp)
+{
+ static struct direct dc;
+ struct dirent *de = readdir (dirp);
+ if (!de) return NIL; /* end of data */
+ dc.d_fileno = 0; /* could get from de->stat.st_ino */
+ dc.d_namlen = strlen (strcpy (dc.d_name,de->d_name));
+ dc.d_reclen = sizeof (dc);
+ return &dc;
+}
diff --git a/imap/src/osdep/unix/os_nto.h b/imap/src/osdep/unix/os_nto.h
new file mode 100644
index 00000000..12ea809a
--- /dev/null
+++ b/imap/src/osdep/unix/os_nto.h
@@ -0,0 +1,75 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- QNX Neutrino RTP version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 September 1993
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/dir.h>
+#include <sys/types.h>
+#include <time.h>
+#include <utime.h>
+
+
+/* QNX gets these wrong */
+
+#define setpgrp setpgid
+#define readdir Readdir
+#define FNDELAY O_NONBLOCK
+#define d_ino d_fileno
+
+
+/* Different names, equivalent things in BSD and SysV */
+
+#ifndef L_SET
+#define L_SET SEEK_SET
+#endif
+#ifndef L_INCR
+#define L_INCR SEEK_CUR
+#endif
+#ifndef L_XTND
+#define L_XTND SEEK_END
+#endif
+
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+long gethostid (void);
+struct direct *Readdir (DIR *dirp);
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (void *d1,void *d2);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_nxt.c b/imap/src/osdep/unix/os_nxt.c
new file mode 100644
index 00000000..a3b749f5
--- /dev/null
+++ b/imap/src/osdep/unix/os_nxt.c
@@ -0,0 +1,54 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- NeXT version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_wait4.c"
+#include "tz_bsd.c"
+#include "strtok.c"
diff --git a/imap/src/osdep/unix/os_nxt.h b/imap/src/osdep/unix/os_nxt.h
new file mode 100644
index 00000000..2af89168
--- /dev/null
+++ b/imap/src/osdep/unix/os_nxt.h
@@ -0,0 +1,50 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- NeXT version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 January 2007
+ */
+
+#include <libc.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+/* Use ours instead of theirs */
+
+#define strtok STRTOK
+#define strtok_r STRTOK_R
+
+char *strtok (char *s,char *ct);
+char *strtok_r (char *s,char *ct,char **r);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_os4.c b/imap/src/osdep/unix/os_os4.c
new file mode 100644
index 00000000..42d90404
--- /dev/null
+++ b/imap/src/osdep/unix/os_os4.c
@@ -0,0 +1,57 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- OSF/Digital UNIX/Tru64 4
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+#include <sia.h>
+#include <siad.h>
+#include <ustat.h>
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "gr_waitp.c"
+#include "tcp_unix.c"
+#include "tz_bsd.c"
+#undef flock
+#include "flocksim.c"
diff --git a/imap/src/osdep/unix/os_os4.h b/imap/src/osdep/unix/os_os4.h
new file mode 100644
index 00000000..432e5316
--- /dev/null
+++ b/imap/src/osdep/unix/os_os4.h
@@ -0,0 +1,51 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- OSF/Digital UNIX/Tru64
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h> /* for struct tm */
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+/* OSF/1 gets this wrong */
+
+#define setpgrp setpgid
+
+#define direct dirent
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_osf.c b/imap/src/osdep/unix/os_osf.c
new file mode 100644
index 00000000..560a3b59
--- /dev/null
+++ b/imap/src/osdep/unix/os_osf.c
@@ -0,0 +1,55 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- OSF/Digital UNIX/Tru64
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+#include <ustat.h>
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "gr_waitp.c"
+#include "tcp_unix.c"
+#include "tz_bsd.c"
+#undef flock
+#include "flocksim.c"
diff --git a/imap/src/osdep/unix/os_osf.h b/imap/src/osdep/unix/os_osf.h
new file mode 100644
index 00000000..432e5316
--- /dev/null
+++ b/imap/src/osdep/unix/os_osf.h
@@ -0,0 +1,51 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- OSF/Digital UNIX/Tru64
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h> /* for struct tm */
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+/* OSF/1 gets this wrong */
+
+#define setpgrp setpgid
+
+#define direct dirent
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_osx.c b/imap/src/osdep/unix/os_osx.c
new file mode 100644
index 00000000..25eb1a7d
--- /dev/null
+++ b/imap/src/osdep/unix/os_osx.c
@@ -0,0 +1,54 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Mac OS X version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "getspnam.c"
+#include "tcp_unix.c"
+#include "gr_wait4.c"
+#include "tz_bsd.c"
diff --git a/imap/src/osdep/unix/os_osx.h b/imap/src/osdep/unix/os_osx.h
new file mode 100644
index 00000000..3480e14a
--- /dev/null
+++ b/imap/src/osdep/unix/os_osx.h
@@ -0,0 +1,56 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Mac OS X version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 26 October 2007
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+/* Mac OS X gets this wrong as of Leopard */
+
+#define setpgrp setpgid
+
+
+#define unix 1
+
+/* Mac OS X security framework also has checkpw, and this causes
+ * multiple-definition problems when building Alpine.
+ */
+
+#define checkpw Checkpw
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_ptx.c b/imap/src/osdep/unix/os_ptx.c
new file mode 100644
index 00000000..d7f84d7d
--- /dev/null
+++ b/imap/src/osdep/unix/os_ptx.c
@@ -0,0 +1,115 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- PTX version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/tiuser.h>
+#include <sys/stropts.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <sys/select.h>
+#include "misc.h"
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+#define DIR_SIZE(d) d->d_reclen
+
+#define toint(c) ((c)-'0')
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#define env_init ENV_INIT
+#include "env_unix.c"
+#undef env_init
+#define getpeername Getpeername
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "flocksim.c"
+#include "scandir.c"
+#include "tz_sv4.c"
+#include "utime.c"
+
+/* Jacket around env_init() to work around PTX inetd braindamage */
+
+static char may_need_server_init = T;
+
+long env_init (char *user,char *home)
+{
+ if (may_need_server_init) { /* maybe need to do server init cruft? */
+ may_need_server_init = NIL; /* not any more we don't */
+ if (!getuid ()) { /* if root, we're most likely a server */
+ t_sync (0); /* PTX inetd is stupid, stupid, stupid */
+ ioctl (0,I_PUSH,"tirdwr");/* it needs this cruft, else servers won't */
+ dup2 (0,1); /* work. How obnoxious!!! */
+ }
+ }
+ ENV_INIT (user,home); /* call the real routine */
+}
+
+/* Emulator for BSD gethostid() call
+ * Returns: unique identifier for this machine
+ */
+
+long gethostid (void)
+{
+ struct sockaddr_in sin;
+ int inet = t_open (TLI_TCP, O_RDWR, 0);
+ if (inet < 0) return 0;
+ getmyinaddr (inet,&sin,sizeof (sin));
+ close (inet);
+ return sin.sin_addr.s_addr;
+}
+
+
+/* Replaced version of getpeername() that jackets into getpeerinaddr()
+ * Accepts: file descriptor
+ * pointer to Internet socket addr
+ * length
+ * Returns: zero if success, data in socket addr
+ */
+
+int Getpeername (int s,struct sockaddr *name,int *namelen)
+{
+ return getpeerinaddr (s,(struct sockaddr_in *) name,*namelen);
+}
diff --git a/imap/src/osdep/unix/os_ptx.h b/imap/src/osdep/unix/os_ptx.h
new file mode 100644
index 00000000..db772597
--- /dev/null
+++ b/imap/src/osdep/unix/os_ptx.h
@@ -0,0 +1,72 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- PTX version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 15 September 2006
+ */
+
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <utime.h>
+#include <dirent.h>
+#include <stropts.h> /* needed in daemons */
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+/* Different names, equivalent things in BSD and SysV */
+
+#define L_SET SEEK_SET
+#define L_INCR SEEK_CUR
+#define L_XTND SEEK_END
+
+#define direct dirent
+#define random lrand48
+
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+long gethostid (void);
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (void *d1,void *d2);
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar);
+int alphasort (void *d1,void *d2);
+long ENV_INIT (char *user,char *home);
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_pyr.c b/imap/src/osdep/unix/os_pyr.c
new file mode 100644
index 00000000..69c8e7e4
--- /dev/null
+++ b/imap/src/osdep/unix/os_pyr.c
@@ -0,0 +1,61 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Pyramid OSx 4.4c version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+#define toint(c) ((c)-'0')
+
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_wait.c"
+#include "memmove.c"
+#include "memset.c"
+#include "strerror.c"
+#include "strpbrk.c"
+#include "strstr.c"
+#include "strtok.c"
+#include "tz_nul.c"
diff --git a/imap/src/osdep/unix/os_pyr.h b/imap/src/osdep/unix/os_pyr.h
new file mode 100644
index 00000000..c7c7e57a
--- /dev/null
+++ b/imap/src/osdep/unix/os_pyr.h
@@ -0,0 +1,58 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Pyramid OSx 4.4c version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 January 2007
+ */
+
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+char *strtok (char *s,char *ct);
+char *strtok_r (char *s,char *ct,char **r);
+char *strstr (char *cs,char *ct);
+char *strpbrk (char *cs,char *ct);
+char *strerror (int n);
+void *memmove (void *s,void *ct,size_t n);
+void *memset (void *s,int c,size_t n);
+void *malloc (size_t byteSize);
+void free (void *ptr);
+void *realloc (void *oldptr,size_t newsize);
+
+int errno;
+
+#define memcpy memmove
+#define strchr index
+#define strrchr rindex
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_qnx.c b/imap/src/osdep/unix/os_qnx.c
new file mode 100644
index 00000000..b1382519
--- /dev/null
+++ b/imap/src/osdep/unix/os_qnx.c
@@ -0,0 +1,77 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- QNX version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1993
+ * Last Edited: 20 December 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include <shadow.h>
+#include <sys/select.h>
+#include "misc.h"
+
+#define DIR_SIZE(d) d->d_reclen
+
+extern char *crypt (const char *pw, const char *salt);
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_sv4.c"
+#include "gethstid.c"
+#include "scandir.c"
+
+/* QNX local readdir()
+ * Accepts: directory structure
+ * Returns: direct struct or NIL if failed
+ */
+
+#undef readdir
+
+struct direct *Readdir (DIR *dirp)
+{
+ static struct direct dc;
+ struct dirent *de = readdir (dirp);
+ if (!de) return NIL; /* end of data */
+ dc.d_fileno = 0; /* could get from de->stat.st_ino */
+ dc.d_namlen = strlen (strcpy (dc.d_name,de->d_name));
+ dc.d_reclen = sizeof (dc);
+ return &dc;
+}
diff --git a/imap/src/osdep/unix/os_qnx.h b/imap/src/osdep/unix/os_qnx.h
new file mode 100644
index 00000000..66ea5e62
--- /dev/null
+++ b/imap/src/osdep/unix/os_qnx.h
@@ -0,0 +1,62 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- QNX version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 September 1993
+ * Last Edited: 20 December 2006
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/file.h>
+#include <sys/dir.h>
+#include <sys/types.h>
+#include </usr/include/unix.h>
+#include <time.h>
+#include <utime.h>
+
+
+/* QNX gets these wrong */
+
+#define setpgrp setpgid
+#define readdir Readdir
+#define FNDELAY O_NONBLOCK
+#define d_ino d_fileno
+
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (void *d1,void *d2);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+
+long gethostid(void);
+struct direct *Readdir (DIR *dirp);
+
+extern long random (void);
diff --git a/imap/src/osdep/unix/os_s40.c b/imap/src/osdep/unix/os_s40.c
new file mode 100644
index 00000000..3dc2fe63
--- /dev/null
+++ b/imap/src/osdep/unix/os_s40.c
@@ -0,0 +1,64 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SUN-OS 4.0 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+#define toint(c) ((c)-'0')
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_wait4.c"
+#include "memmove.c"
+#include "strerror.c"
+#define strstr Strstr /* override SUN's broken version */
+#include "strstr.c"
+#include "strtoul.c"
+#include "tz_bsd.c"
diff --git a/imap/src/osdep/unix/os_s40.h b/imap/src/osdep/unix/os_s40.h
new file mode 100644
index 00000000..a67d923f
--- /dev/null
+++ b/imap/src/osdep/unix/os_s40.h
@@ -0,0 +1,33 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SUN-OS 4.0 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+void *malloc (size_t byteSize);
+void free (void *ptr);
+void *realloc (void *oldptr,size_t newsize);
+
+#include "os_sun.h" /* now use regular SUN-OS file */
diff --git a/imap/src/osdep/unix/os_sc5.c b/imap/src/osdep/unix/os_sc5.c
new file mode 100644
index 00000000..11ac69de
--- /dev/null
+++ b/imap/src/osdep/unix/os_sc5.c
@@ -0,0 +1,63 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SCO Unix version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include <sys/time.h> /* must be before osdep.h */
+#include "mail.h"
+#include <stdio.h>
+#include "osdep.h"
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+#include "misc.h"
+#define SecureWare /* protected subsystem */
+#include <sys/security.h>
+#include <sys/audit.h>
+#include <prot.h>
+#include <pwd.h>
+
+#define DIR_SIZE(d) d->d_reclen
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "flocksim.c"
+#include "scandir.c"
+#include "tz_sv4.c"
+#include "gethstid.c"
+#undef setpgrp
+#include "setpgrp.c"
+#include "rename.c"
+#include "utime.c"
diff --git a/imap/src/osdep/unix/os_sc5.h b/imap/src/osdep/unix/os_sc5.h
new file mode 100644
index 00000000..f81e4961
--- /dev/null
+++ b/imap/src/osdep/unix/os_sc5.h
@@ -0,0 +1,76 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SCO Unix version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 20 December 2006
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <utime.h>
+#include <dirent.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+/* SCO gets this wrong */
+
+#define setpgrp Setpgrp
+int Setpgrp (int pid,int gid);
+
+#define rename Rename
+
+
+/* Different names, equivalent things in BSD and SysV */
+
+#define L_SET SEEK_SET
+#define L_INCR SEEK_CUR
+#define L_XTND SEEK_END
+
+#define direct dirent
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+
+long gethostid (void);
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (void *d1,void *d2);
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar);
+int alphasort (void *d1,void *d2);
+int fsync (int fd);
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_sco.c b/imap/src/osdep/unix/os_sco.c
new file mode 100644
index 00000000..fb082420
--- /dev/null
+++ b/imap/src/osdep/unix/os_sco.c
@@ -0,0 +1,66 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SCO Unix version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include <sys/time.h> /* must be before osdep.h */
+#include "mail.h"
+#include <stdio.h>
+#include "osdep.h"
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+#include "misc.h"
+#define SecureWare /* protected subsystem */
+#include <sys/security.h>
+#include <sys/audit.h>
+#include <prot.h>
+#include <pwd.h>
+
+char *bigcrypt (char *key,char *salt);
+
+#define DIR_SIZE(d) d->d_reclen
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "flocksim.c"
+#include "scandir.c"
+#include "tz_sv4.c"
+#include "gethstid.c"
+#include "fsync.c"
+#undef setpgrp
+#include "setpgrp.c"
+#include "rename.c"
+#include "utime.c"
diff --git a/imap/src/osdep/unix/os_sco.h b/imap/src/osdep/unix/os_sco.h
new file mode 100644
index 00000000..6bcd5376
--- /dev/null
+++ b/imap/src/osdep/unix/os_sco.h
@@ -0,0 +1,79 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SCO Unix version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 20 December 2006
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <utime.h>
+#include <dirent.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+/* SCO gets this wrong */
+
+#define setpgrp Setpgrp
+int Setpgrp (int pid,int gid);
+
+#define rename Rename
+
+
+/* Different names, equivalent things in BSD and SysV */
+
+#define L_SET SEEK_SET
+#define L_INCR SEEK_CUR
+#define L_XTND SEEK_END
+
+#define direct dirent
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+#define ftruncate chsize
+#define random rand
+
+
+long gethostid (void);
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (void *d1,void *d2);
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar);
+int alphasort (void *d1,void *d2);
+int fsync (int fd);
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_sgi.c b/imap/src/osdep/unix/os_sgi.c
new file mode 100644
index 00000000..3e5eb550
--- /dev/null
+++ b/imap/src/osdep/unix/os_sgi.c
@@ -0,0 +1,57 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SGI version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <bstring.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_nul.c"
+#include "flocksim.c"
+#undef setpgrp
+#include "setpgrp.c"
+#include "utime.c"
diff --git a/imap/src/osdep/unix/os_sgi.h b/imap/src/osdep/unix/os_sgi.h
new file mode 100644
index 00000000..22664858
--- /dev/null
+++ b/imap/src/osdep/unix/os_sgi.h
@@ -0,0 +1,59 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SGI version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 20 December 2006
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h> /* for struct tm */
+#include <utime.h>
+#include <fcntl.h>
+#include <sys/syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+/* Many versions of SysV get this wrong */
+
+#define setpgrp(a,b) Setpgrp(a,b)
+int Setpgrp (int pid,int gid);
+
+
+#define direct dirent
+
+#define fatal cclient_fatal
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_shp.c b/imap/src/osdep/unix/os_shp.c
new file mode 100644
index 00000000..eb7e4e80
--- /dev/null
+++ b/imap/src/osdep/unix/os_shp.c
@@ -0,0 +1,79 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- HP/UX version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 16 August 2007
+ */
+
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+#define toint(c) ((c)-'0')
+
+#include <stdio.h>
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+extern char *sys_errlist[];
+extern int sys_nerr;
+#include <pwd.h>
+#include <hpsecurity.h>
+#include <prot.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "flocksim.c"
+#include "tz_sv4.c"
+#undef setpgrp
+#include "setpgrp.c"
+#include "utime.c"
+
+/* Emulator for BSD gethostid() call
+ * Returns: a unique identifier for the system.
+ * Even though HP/UX has an undocumented gethostid() system call,
+ * it does not work (at least for non-privileged users).
+ */
+
+long gethostid (void)
+{
+ struct utsname udata;
+ return (uname (&udata)) ? 0xfeedface : atol (udata.__idnumber);
+}
diff --git a/imap/src/osdep/unix/os_shp.h b/imap/src/osdep/unix/os_shp.h
new file mode 100644
index 00000000..d51dba53
--- /dev/null
+++ b/imap/src/osdep/unix/os_shp.h
@@ -0,0 +1,63 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- HP/UX version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 20 December 2006
+ */
+
+#include <string.h>
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <utime.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+#define direct dirent
+#define random lrand48
+
+
+/* Many versions of SysV get this wrong */
+
+#define setpgrp(a,b) Setpgrp(a,b)
+int Setpgrp (int pid,int gid);
+
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+long gethostid (void);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_slx.c b/imap/src/osdep/unix/os_slx.c
new file mode 100644
index 00000000..c94d6322
--- /dev/null
+++ b/imap/src/osdep/unix/os_slx.c
@@ -0,0 +1,56 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- modern Linux version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1993
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include <shadow.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "getspnam.c" /* has socklen_t in spite of man page?? */
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_sv4.c"
+#include "flocklnx.c"
diff --git a/imap/src/osdep/unix/os_slx.h b/imap/src/osdep/unix/os_slx.h
new file mode 100644
index 00000000..b5f39ffa
--- /dev/null
+++ b/imap/src/osdep/unix/os_slx.h
@@ -0,0 +1,67 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Linux version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 September 1993
+ * Last Edited: 30 August 2006
+ */
+
+/*
+ *** These lines are claimed to be necessary to build on Debian Linux on an
+ *** Alpha.
+ */
+
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE 1
+#endif /* _XOPEN_SOURCE */
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE 1
+#endif /* _BSD_SOURCE */
+
+/* end Debian Linux on Alpha strangeness */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h> /* for struct tm */
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+/* Linux gets this wrong */
+
+#define setpgrp setpgid
+
+#define direct dirent
+
+#define flock safe_flock
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_sol.c b/imap/src/osdep/unix/os_sol.c
new file mode 100644
index 00000000..7a78ef80
--- /dev/null
+++ b/imap/src/osdep/unix/os_sol.c
@@ -0,0 +1,71 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Solaris version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 1992
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/tiuser.h>
+#include <sys/stropts.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <crypt.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include "misc.h"
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+#define DIR_SIZE(d) d->d_reclen
+
+#define toint(c) ((c)-'0')
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "getspnam.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "flocksim.c"
+#include "scandir.c"
+#include "tz_sv4.c"
+#include "gethstid.c"
+#undef setpgrp
+#include "setpgrp.c"
+#include "utime.c"
diff --git a/imap/src/osdep/unix/os_soln.h b/imap/src/osdep/unix/os_soln.h
new file mode 100644
index 00000000..969dcbbb
--- /dev/null
+++ b/imap/src/osdep/unix/os_soln.h
@@ -0,0 +1,87 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Solaris version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 20 December 2006
+ */
+
+#include <string.h>
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <utime.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+/* Many versions of SysV get this wrong */
+
+#define setpgrp(a,b) Setpgrp(a,b)
+int Setpgrp (int pid,int gid);
+
+
+/* Different names, equivalent things in BSD and SysV */
+
+/* L_SET is defined for some strange reason in <sys/file.h> on SVR4. */
+#ifndef L_SET
+#define L_SET SEEK_SET
+#endif
+#ifndef L_INCR
+#define L_INCR SEEK_CUR
+#endif
+#ifndef L_XTND
+#define L_XTND SEEK_END
+#endif
+
+#define direct dirent
+#define random lrand48
+
+#define scandir Scandir
+#define alphasort Alphasort
+
+#define getpass getpassphrase
+
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+long gethostid (void);
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (const void *d1,const void *d2);
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar);
+int alphasort (void *d1,void *d2);
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_solo.h b/imap/src/osdep/unix/os_solo.h
new file mode 100644
index 00000000..c3fe4a84
--- /dev/null
+++ b/imap/src/osdep/unix/os_solo.h
@@ -0,0 +1,84 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Solaris version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 20 December 2006
+ */
+
+#include <string.h>
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <utime.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+/* Many versions of SysV get this wrong */
+
+#define setpgrp(a,b) Setpgrp(a,b)
+int Setpgrp (int pid,int gid);
+
+
+/* Different names, equivalent things in BSD and SysV */
+
+/* L_SET is defined for some strange reason in <sys/file.h> on SVR4. */
+#ifndef L_SET
+#define L_SET SEEK_SET
+#endif
+#ifndef L_INCR
+#define L_INCR SEEK_CUR
+#endif
+#ifndef L_XTND
+#define L_XTND SEEK_END
+#endif
+
+#define direct dirent
+#define random lrand48
+
+#define scandir Scandir
+#define alphasort Alphasort
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+long gethostid (void);
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (const void *d1,const void *d2);
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar);
+int alphasort (void *d1,void *d2);
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_sos.c b/imap/src/osdep/unix/os_sos.c
new file mode 100644
index 00000000..8256aafa
--- /dev/null
+++ b/imap/src/osdep/unix/os_sos.c
@@ -0,0 +1,57 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- S OSF/Digital UNIX/Tru64
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+#include <sys/security.h>
+#include <prot.h>
+#include <ustat.h>
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_bsd.c"
+#undef flock
+#include "flocksim.c"
diff --git a/imap/src/osdep/unix/os_sos.h b/imap/src/osdep/unix/os_sos.h
new file mode 100644
index 00000000..432e5316
--- /dev/null
+++ b/imap/src/osdep/unix/os_sos.h
@@ -0,0 +1,51 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- OSF/Digital UNIX/Tru64
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h> /* for struct tm */
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+/* OSF/1 gets this wrong */
+
+#define setpgrp setpgid
+
+#define direct dirent
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_sua.c b/imap/src/osdep/unix/os_sua.c
new file mode 100644
index 00000000..dcf9311b
--- /dev/null
+++ b/imap/src/osdep/unix/os_sua.c
@@ -0,0 +1,54 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Windows Vista SUA
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1993
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_sv4.c"
+#include "gethstid.c"
diff --git a/imap/src/osdep/unix/os_sua.h b/imap/src/osdep/unix/os_sua.h
new file mode 100644
index 00000000..f4150d4f
--- /dev/null
+++ b/imap/src/osdep/unix/os_sua.h
@@ -0,0 +1,50 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Windows Vista SUA
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 September 1993
+ * Last Edited: 4 May 2007
+ */
+
+#define _REENTRANT /* for strtok_r() */
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h> /* for struct tm */
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+#define setpgrp setpgid
+
+#define direct dirent
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_sun.c b/imap/src/osdep/unix/os_sun.c
new file mode 100644
index 00000000..5348ef58
--- /dev/null
+++ b/imap/src/osdep/unix/os_sun.c
@@ -0,0 +1,64 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SUN-OS version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+#define toint(c) ((c)-'0')
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "memmove.c"
+#include "strerror.c"
+#define strstr Strstr /* override SUN's broken version */
+#include "strstr.c"
+#include "strtoul.c"
+#include "tz_bsd.c"
diff --git a/imap/src/osdep/unix/os_sun.h b/imap/src/osdep/unix/os_sun.h
new file mode 100644
index 00000000..c7d72ace
--- /dev/null
+++ b/imap/src/osdep/unix/os_sun.h
@@ -0,0 +1,51 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SUN-OS version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+#define strstr Strstr /* override system definition */
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+
+char *Strstr (char *cs,char *ct);
+char *strerror (int n);
+unsigned long strtoul (char *s,char **endp,int base);
+#define memcpy memmove
+void *memmove (void *s,void *ct,size_t n);
+void *memset (void *s,int c,size_t n);
diff --git a/imap/src/osdep/unix/os_sv2.c b/imap/src/osdep/unix/os_sv2.c
new file mode 100644
index 00000000..14e3951b
--- /dev/null
+++ b/imap/src/osdep/unix/os_sv2.c
@@ -0,0 +1,128 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SVR2 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 1992
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+extern int errno;
+#include <pwd.h>
+#include <sys/socket.h>
+#include <time.h>
+#define KERNEL
+#include <sys/time.h>
+#undef KERNEL
+#include "misc.h"
+
+#define DIR_SIZE(d) sizeof (DIR)
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+#define toint(c) ((c)-'0')
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+
+#define NBBY 8 /* number of bits in a byte */
+#define FD_SETSIZE 256
+
+typedef long fd_mask;
+#define NFDBITS (sizeof(fd_mask) * NBBY)
+ /* bits per mask */
+#define howmany(x, y) (((x)+((y)-1))/(y))
+
+typedef struct fd_set {
+ fd_mask fds_bits[howmany(FD_SETSIZE, NFDBITS)];
+} fd_set;
+
+#define FD_SET(n, p) ((p)->fds_bits[(n)/NFDBITS] |= \
+ (1 << ((n) % NFDBITS)))
+#define FD_CLR(n, p) ((p)->fds_bits[(n)/NFDBITS] &= \
+ ~(1 << ((n) % NFDBITS)))
+#define FD_ISSET(n, p) ((p)->fds_bits[(n)/NFDBITS] & \
+ (1 << ((n) % NFDBITS)))
+#define FD_ZERO(p) bzero((char *)(p), sizeof(*(p)))
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_wait.c"
+#include "flocksim.c"
+#include "opendir.c"
+#include "scandir.c"
+#include "memmove2.c"
+#include "strstr.c"
+#include "strerror.c"
+#include "strtoul.c"
+#include "tz_sv4.c"
+#include "gethstid.c"
+#include "fsync.c"
+#undef setpgrp
+#include "setpgrp.c"
+
+/* Emulator for BSD syslog() routine
+ * Accepts: priority
+ * message
+ * parameters
+ */
+
+int syslog (int priority,char *message,char *parameters)
+{
+ /* nothing here for now */
+}
+
+
+/* Emulator for BSD openlog() routine
+ * Accepts: identity
+ * options
+ * facility
+ */
+
+int openlog (char *ident,int logopt,int facility)
+{
+ /* nothing here for now */
+}
+
+
+/* Emulator for BSD ftruncate() routine
+ * Accepts: file descriptor
+ * length
+ */
+
+int ftruncate (int fd,unsigned long length)
+{
+ return -1; /* gotta figure out how to do this */
+}
diff --git a/imap/src/osdep/unix/os_sv2.h b/imap/src/osdep/unix/os_sv2.h
new file mode 100644
index 00000000..c39904a4
--- /dev/null
+++ b/imap/src/osdep/unix/os_sv2.h
@@ -0,0 +1,120 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SVR2 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 1992
+ * Last Edited: 20 December 2006
+ */
+
+#include <unistd.h>
+#include <string.h>
+#define char void
+#include <memory.h>
+#undef char
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+/* Many versions of SysV get this wrong */
+
+#define setpgrp(a,b) Setpgrp(a,b)
+int Setpgrp (int pid,int gid);
+
+
+/* Different names between BSD and SVR4 */
+
+#define L_SET SEEK_SET
+#define L_INCR SEEK_CUR
+#define L_XTND SEEK_END
+
+#define lstat stat
+#define random lrand48
+
+#define SIGSTOP SIGQUIT
+
+#define S_IFLNK 0120000
+
+
+/* syslog() emulation */
+
+#define LOG_MAIL (2<<3) /* mail system */
+#define LOG_DAEMON (3<<3) /* system daemons */
+#define LOG_AUTH (4<<3) /* security/authorization messages */
+#define LOG_ALERT 1 /* action must be taken immediately */
+#define LOG_CONS 0x02 /* log on the console if errors in sending */
+#define LOG_ODELAY 0x04 /* delay open until syslog() is called */
+#define LOG_NDELAY 0x08 /* don't delay open */
+#define LOG_NOWAIT 0x10 /* if forking to log on console, don't wait() */
+
+
+/* For setitimer() emulation */
+
+#define ITIMER_REAL 0
+
+
+/* For opendir() emulation */
+
+typedef struct _dirdesc {
+ int dd_fd;
+ long dd_loc;
+ long dd_size;
+ char *dd_buf;
+} DIR;
+
+struct passwd *getpwent (void);
+struct passwd *getpwuid (int uid);
+struct passwd *getpwnam (char *name);
+struct group *getgrnam (char *name);
+
+char *getenv (char *name);
+long gethostid (void);
+void *memmove (void *s,void *ct,size_t n);
+char *strstr (char *cs,char *ct);
+char *strerror (int n);
+unsigned long strtoul (char *s,char **endp,int base);
+DIR *opendir (char * name);
+int closedir (DIR *d);
+struct direct *readdir (DIR *d);
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (void *d1,void *d2);
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar);
+int alphasort (void *d1,void *d2);
+int fsync (int fd);
+int openlog (ident,logopt,facility);
+int syslog (priority,message,parameters ...);
+void *malloc (size_t byteSize);
+void free (void *ptr);
+void *realloc (void *oldptr,size_t newsize);
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_sv4.c b/imap/src/osdep/unix/os_sv4.c
new file mode 100644
index 00000000..4b8dd12f
--- /dev/null
+++ b/imap/src/osdep/unix/os_sv4.c
@@ -0,0 +1,68 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SVR4 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 1992
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/tiuser.h>
+#include <sys/stropts.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include "misc.h"
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+#define DIR_SIZE(d) d->d_reclen
+
+#define toint(c) ((c)-'0')
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "flocksim.c"
+#include "scandir.c"
+#include "tz_sv4.c"
+#include "gethstid.c"
+#undef setpgrp
+#include "setpgrp.c"
+#include "utime.c"
diff --git a/imap/src/osdep/unix/os_sv4.h b/imap/src/osdep/unix/os_sv4.h
new file mode 100644
index 00000000..a11ad15d
--- /dev/null
+++ b/imap/src/osdep/unix/os_sv4.h
@@ -0,0 +1,78 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- SVR4 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 April 1992
+ * Last Edited: 20 December 2006
+ */
+
+#include <string.h>
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <utime.h>
+#include <syslog.h>
+#include <sys/file.h>
+#include <ustat.h>
+
+
+/* Many versions of SysV get this wrong */
+
+#define setpgrp(a,b) Setpgrp(a,b)
+int Setpgrp (int pid,int gid);
+
+
+/* Different names, equivalent things in BSD and SysV */
+
+/* L_SET is defined for some strange reason in <sys/file.h> on SVR4. */
+#ifndef L_SET
+#define L_SET SEEK_SET
+#endif
+#define L_INCR SEEK_CUR
+#define L_XTND SEEK_END
+
+#define direct dirent
+#define random lrand48
+
+
+#define utime portable_utime
+int portable_utime (char *file,time_t timep[2]);
+
+long gethostid (void);
+typedef int (*select_t) (struct direct *name);
+typedef int (*compar_t) (void *d1,void *d2);
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar);
+int alphasort (void *d1,void *d2);
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+#include "flocksim.h"
diff --git a/imap/src/osdep/unix/os_ult.c b/imap/src/osdep/unix/os_ult.c
new file mode 100644
index 00000000..28b72ecc
--- /dev/null
+++ b/imap/src/osdep/unix/os_ult.c
@@ -0,0 +1,52 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Ultrix version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ *
+ * Date: 11 May 1989
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_waitp.c"
+#include "tz_bsd.c"
diff --git a/imap/src/osdep/unix/os_ult.h b/imap/src/osdep/unix/os_ult.h
new file mode 100644
index 00000000..3b93f6db
--- /dev/null
+++ b/imap/src/osdep/unix/os_ult.h
@@ -0,0 +1,42 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- Ultrix version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/syslog.h>
+#include <sys/file.h>
+
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/os_vu2.c b/imap/src/osdep/unix/os_vu2.c
new file mode 100644
index 00000000..be574e07
--- /dev/null
+++ b/imap/src/osdep/unix/os_vu2.c
@@ -0,0 +1,82 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- VAX Ultrix 2.3 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ *
+ * Date: 11 May 1989
+ * Last Edited: 16 August 2007
+ */
+
+#include "tcp_unix.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <pwd.h>
+#include "misc.h"
+
+#define NFDBITS (sizeof(long) * 8)
+
+#define FD_SET(n, p) ((p)->fds_bits[(n)/NFDBITS] |= \
+ (1 << ((n) % NFDBITS)))
+#define FD_CLR(n, p) ((p)->fds_bits[(n)/NFDBITS] &= \
+ ~(1 << ((n) % NFDBITS)))
+#define FD_ISSET(n, p) ((p)->fds_bits[(n)/NFDBITS] & \
+ (1 << ((n) % NFDBITS)))
+#define FD_ZERO(p) bzero((char *)(p), sizeof(*(p)))
+
+
+/* Old Ultrix has its own wierd inet_addr() that returns a in_addr struct. */
+
+/* Portable inet_addr () that returns a u_long
+ * Accepts: dotted host string
+ * Returns: u_long
+ */
+
+u_long portable_inet_addr (char *hostname)
+{
+ struct in_addr *in = &inet_addr (hostname);
+ return in->s_addr;
+}
+
+
+#define inet_addr portable_inet_addr
+
+#include "fs_unix.c"
+#include "ftl_unix.c"
+#include "nl_unix.c"
+#include "env_unix.c"
+#define fork vfork
+#include "tcp_unix.c"
+#include "gr_wait.c"
+#include "memmove.c"
+#include "strerror.c"
+#include "strstr.c"
+#include "strtoul.c"
+#include "tz_nul.c"
diff --git a/imap/src/osdep/unix/os_vu2.h b/imap/src/osdep/unix/os_vu2.h
new file mode 100644
index 00000000..7721e092
--- /dev/null
+++ b/imap/src/osdep/unix/os_vu2.h
@@ -0,0 +1,79 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- VAX Ultrix 2.3 version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include <memory.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <string.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/file.h>
+
+
+/* syslog() emulation */
+
+#define LOG_MAIL (2<<3) /* mail system */
+#define LOG_DAEMON (3<<3) /* system daemons */
+#define LOG_AUTH (4<<3) /* security/authorization messages */
+#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 signification condition */
+#define LOG_INFO 6 /* informational */
+#define LOG_DEBUG 7 /* debug-level messages */
+#define LOG_PID 0x01 /* log the pid with each message */
+#define LOG_CONS 0x02 /* log on the console if errors in sending */
+#define LOG_ODELAY 0x04 /* delay open until syslog() is called */
+#define LOG_NDELAY 0x08 /* don't delay open */
+#define LOG_NOWAIT 0x10 /* if forking to log on console, don't wait() */
+
+
+#define isodigit(c) (((unsigned)(c)>=060)&((unsigned)(c)<=067))
+#define toint(c) ((c)-'0')
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+
+char *getenv (char *name);
+char *strstr (char *cs,char *ct);
+char *strerror (int n);
+void *memmove (void *s,void *ct,size_t n);
+unsigned long strtoul (char *s,char **endp,int base);
+void *malloc (size_t byteSize);
+void free (void *ptr);
+void *realloc (void *oldptr,size_t newsize);
+u_long portable_inet_addr (char *hostname);
+
+#include "env_unix.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
diff --git a/imap/src/osdep/unix/phile.c b/imap/src/osdep/unix/phile.c
new file mode 100644
index 00000000..ce72d0a9
--- /dev/null
+++ b/imap/src/osdep/unix/phile.c
@@ -0,0 +1,553 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: File routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 25 August 1993
+ * Last Edited: 9 May 2006
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <signal.h>
+#include "mail.h"
+#include "osdep.h"
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "rfc822.h"
+#include "misc.h"
+#include "dummy.h"
+
+/* Types returned from phile_type() */
+
+#define PTYPEBINARY 0 /* binary data */
+#define PTYPETEXT 1 /* textual data */
+#define PTYPECRTEXT 2 /* textual data with CR */
+#define PTYPE8 4 /* textual 8bit data */
+#define PTYPEISO2022JP 8 /* textual Japanese */
+#define PTYPEISO2022KR 16 /* textual Korean */
+#define PTYPEISO2022CN 32 /* textual Chinese */
+
+
+/* PHILE I/O stream local data */
+
+typedef struct phile_local {
+ ENVELOPE *env; /* file envelope */
+ BODY *body; /* file body */
+ char tmp[MAILTMPLEN]; /* temporary buffer */
+} PHILELOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((PHILELOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *phile_valid (char *name);
+int phile_isvalid (char *name,char *tmp);
+void *phile_parameters (long function,void *value);
+void phile_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void phile_list (MAILSTREAM *stream,char *ref,char *pat);
+void phile_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long phile_create (MAILSTREAM *stream,char *mailbox);
+long phile_delete (MAILSTREAM *stream,char *mailbox);
+long phile_rename (MAILSTREAM *stream,char *old,char *newname);
+long phile_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *phile_open (MAILSTREAM *stream);
+int phile_type (unsigned char *s,unsigned long i,unsigned long *j);
+void phile_close (MAILSTREAM *stream,long options);
+ENVELOPE *phile_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body,
+ long flags);
+char *phile_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long phile_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+long phile_ping (MAILSTREAM *stream);
+void phile_check (MAILSTREAM *stream);
+long phile_expunge (MAILSTREAM *stream,char *sequence,long options);
+long phile_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long phile_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+/* File routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER philedriver = {
+ "phile", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_READONLY|DR_NOSTICKY,
+ (DRIVER *) NIL, /* next driver */
+ phile_valid, /* mailbox is valid for us */
+ phile_parameters, /* manipulate parameters */
+ phile_scan, /* scan mailboxes */
+ phile_list, /* list mailboxes */
+ phile_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ dummy_delete, /* delete mailbox */
+ dummy_rename, /* rename mailbox */
+ phile_status, /* status of mailbox */
+ phile_open, /* open mailbox */
+ phile_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ phile_structure, /* fetch message envelopes */
+ phile_header, /* fetch message header only */
+ phile_text, /* fetch message body only */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ phile_ping, /* ping mailbox to see if still alive */
+ phile_check, /* check for new messages */
+ phile_expunge, /* expunge deleted messages */
+ phile_copy, /* copy messages to another mailbox */
+ phile_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM phileproto = {&philedriver};
+
+/* File validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *phile_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return phile_isvalid (name,tmp) ? &philedriver : NIL;
+}
+
+
+/* File test for valid mailbox
+ * Accepts: mailbox name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int phile_isvalid (char *name,char *tmp)
+{
+ struct stat sbuf;
+ char *s;
+ /* INBOX never accepted, any other name is */
+ return ((s = mailboxfile (tmp,name)) && *s && !stat (s,&sbuf) &&
+ !(sbuf.st_mode & S_IFDIR) &&
+ /* only allow empty files if no empty proto
+ or if #ftp */
+ (sbuf.st_size || !default_proto (T) ||
+ ((*name == '#') && ((name[1] == 'f') || (name[1] == 'F')) &&
+ ((name[2] == 't') || (name[2] == 'T')) &&
+ ((name[3] == 'p') || (name[3] == 'P')) && (name[4] == '/'))));
+}
+
+/* File manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *phile_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* File mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void phile_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* File list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void phile_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* File list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void phile_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+
+/* File status
+ * Accepts: mail stream
+ * mailbox name
+ * status flags
+ * Returns: T on success, NIL on failure
+ */
+
+long phile_status (MAILSTREAM *stream,char *mbx,long flags)
+{
+ char *s,tmp[MAILTMPLEN];
+ MAILSTATUS status;
+ struct stat sbuf;
+ long ret = NIL;
+ if ((s = mailboxfile (tmp,mbx)) && *s && !stat (s,&sbuf)) {
+ status.flags = flags; /* return status values */
+ status.unseen = (stream && mail_elt (stream,1)->seen) ? 0 : 1;
+ status.messages = status.recent = status.uidnext = 1;
+ status.uidvalidity = sbuf.st_mtime;
+ /* pass status to main program */
+ mm_status (stream,mbx,&status);
+ ret = LONGT; /* success */
+ }
+ return ret;
+}
+
+/* File open
+ * Accepts: Stream to open
+ * Returns: Stream on success, NIL on failure
+ */
+
+MAILSTREAM *phile_open (MAILSTREAM *stream)
+{
+ int i,k,fd;
+ unsigned long j,m;
+ char *s,tmp[MAILTMPLEN];
+ struct passwd *pw;
+ struct stat sbuf;
+ struct tm *t;
+ MESSAGECACHE *elt;
+ SIZEDTEXT *buf;
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &phileproto;
+ if (stream->local) fatal ("phile recycle stream");
+ /* open associated file */
+ if (!mailboxfile (tmp,stream->mailbox) || !tmp[0] || stat (tmp,&sbuf) ||
+ (fd = open (tmp,O_RDONLY,NIL)) < 0) {
+ sprintf (tmp,"Unable to open file %s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr (tmp);
+ stream->local = fs_get (sizeof (PHILELOCAL));
+ mail_exists (stream,1); /* make sure upper level knows */
+ mail_recent (stream,1);
+ elt = mail_elt (stream,1); /* instantiate cache element */
+ elt->valid = elt->recent = T; /* mark valid flags */
+ stream->sequence++; /* bump sequence number */
+ stream->rdonly = T; /* make sure upper level knows readonly */
+ /* instantiate a new envelope and body */
+ LOCAL->env = mail_newenvelope ();
+ LOCAL->body = mail_newbody ();
+
+ t = gmtime (&sbuf.st_mtime); /* get UTC time and Julian day */
+ i = t->tm_hour * 60 + t->tm_min;
+ k = t->tm_yday;
+ t = localtime(&sbuf.st_mtime);/* get local time */
+ /* calculate time delta */
+ i = t->tm_hour * 60 + t->tm_min - i;
+ if (k = t->tm_yday - k) i += ((k < 0) == (abs (k) == 1)) ? -24*60 : 24*60;
+ k = abs (i); /* time from UTC either way */
+ elt->hours = t->tm_hour; elt->minutes = t->tm_min; elt->seconds = t->tm_sec;
+ elt->day = t->tm_mday; elt->month = t->tm_mon + 1;
+ elt->year = t->tm_year - (BASEYEAR - 1900);
+ elt->zoccident = (k == i) ? 0 : 1;
+ elt->zhours = k/60;
+ elt->zminutes = k % 60;
+ sprintf (tmp,"%s, %d %s %d %02d:%02d:%02d %c%02d%02d",
+ days[t->tm_wday],t->tm_mday,months[t->tm_mon],t->tm_year+1900,
+ t->tm_hour,t->tm_min,t->tm_sec,elt->zoccident ? '-' : '+',
+ elt->zhours,elt->zminutes);
+ /* set up Date field */
+ LOCAL->env->date = cpystr (tmp);
+
+ /* fill in From field from file owner */
+ LOCAL->env->from = mail_newaddr ();
+ if (pw = getpwuid (sbuf.st_uid)) strcpy (tmp,pw->pw_name);
+ else sprintf (tmp,"User-Number-%ld",(long) sbuf.st_uid);
+ LOCAL->env->from->mailbox = cpystr (tmp);
+ LOCAL->env->from->host = cpystr (mylocalhost ());
+ /* set subject to be mailbox name */
+ LOCAL->env->subject = cpystr (stream->mailbox);
+ /* slurp the data */
+ (buf = &elt->private.special.text)->size = sbuf.st_size;
+ read (fd,buf->data = (unsigned char *) fs_get (buf->size + 1),buf->size);
+ buf->data[buf->size] = '\0';
+ close (fd); /* close the file */
+ /* analyze data type */
+ if (i = phile_type (buf->data,buf->size,&j)) {
+ LOCAL->body->type = TYPETEXT;
+ LOCAL->body->subtype = cpystr ("PLAIN");
+ if (!(i & PTYPECRTEXT)) { /* change Internet newline format as needed */
+ s = (char *) buf->data; /* make copy of UNIX-format string */
+ buf->data = NIL; /* zap the buffer */
+ buf->size = strcrlfcpy (&buf->data,&m,s,buf->size);
+ fs_give ((void **) &s); /* flush original UNIX-format string */
+ }
+ LOCAL->body->parameter = mail_newbody_parameter ();
+ LOCAL->body->parameter->attribute = cpystr ("charset");
+ LOCAL->body->parameter->value =
+ cpystr ((i & PTYPEISO2022JP) ? "ISO-2022-JP" :
+ (i & PTYPEISO2022KR) ? "ISO-2022-KR" :
+ (i & PTYPEISO2022CN) ? "ISO-2022-CN" :
+ (i & PTYPE8) ? "X-UNKNOWN" : "US-ASCII");
+ LOCAL->body->encoding = (i & PTYPE8) ? ENC8BIT : ENC7BIT;
+ LOCAL->body->size.lines = j;
+ }
+ else { /* binary data */
+ LOCAL->body->type = TYPEAPPLICATION;
+ LOCAL->body->subtype = cpystr ("OCTET-STREAM");
+ LOCAL->body->parameter = mail_newbody_parameter ();
+ LOCAL->body->parameter->attribute = cpystr ("name");
+ LOCAL->body->parameter->value =
+ cpystr ((s = (strrchr (stream->mailbox,'/'))) ? s+1 : stream->mailbox);
+ LOCAL->body->encoding = ENCBASE64;
+ buf->data = rfc822_binary (s = (char *) buf->data,buf->size,&buf->size);
+ fs_give ((void **) &s); /* flush originary binary contents */
+ }
+ phile_header (stream,1,&j,NIL);
+ LOCAL->body->size.bytes = LOCAL->body->contents.text.size = buf->size;
+ elt->rfc822_size = j + buf->size;
+ /* only one message ever... */
+ stream->uid_validity = sbuf.st_mtime;
+ stream->uid_last = elt->private.uid = 1;
+ return stream; /* return stream alive to caller */
+}
+
+/* File determine data type
+ * Accepts: data to examine
+ * size of data
+ * pointer to line count return
+ * Returns: PTYPE mask of data type
+ */
+
+int phile_type (unsigned char *s,unsigned long i,unsigned long *j)
+{
+ int ret = PTYPETEXT;
+ char *charvec = "bbbbbbbaaalaacaabbbbbbbbbbbebbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+ *j = 0; /* no lines */
+ /* check type of every character */
+ while (i--) switch (charvec[*s++]) {
+ case 'A':
+ ret |= PTYPE8; /* 8bit character */
+ break;
+ case 'a':
+ break; /* ASCII character */
+ case 'b':
+ return PTYPEBINARY; /* binary byte seen, stop immediately */
+ case 'c':
+ ret |= PTYPECRTEXT; /* CR indicates Internet text */
+ break;
+ case 'e': /* ESC */
+ if (*s == '$') { /* ISO-2022 sequence? */
+ switch (s[1]) {
+ case 'B': case '@': ret |= PTYPEISO2022JP; break;
+ case ')':
+ switch (s[2]) {
+ case 'A': case 'E': case 'G': ret |= PTYPEISO2022CN; break;
+ case 'C': ret |= PTYPEISO2022KR; break;
+ }
+ case '*':
+ switch (s[2]) {
+ case 'H': ret |= PTYPEISO2022CN; break;
+ }
+ case '+':
+ switch (s[2]) {
+ case 'I': case 'J': case 'K': case 'L': case 'M':
+ ret |= PTYPEISO2022CN; break;
+ }
+ }
+ }
+ break;
+ case 'l': /* newline */
+ (*j)++;
+ break;
+ }
+ return ret; /* return type of data */
+}
+
+/* File close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void phile_close (MAILSTREAM *stream,long options)
+{
+ if (LOCAL) { /* only if a file is open */
+ fs_give ((void **) &mail_elt (stream,1)->private.special.text.data);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* File fetch structure
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to return body
+ * option flags
+ * Returns: envelope of this message, body returned in body value
+ *
+ * Fetches the "fast" information as well
+ */
+
+ENVELOPE *phile_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body,
+ long flags)
+{
+ if (body) *body = LOCAL->body;
+ return LOCAL->env; /* return the envelope */
+}
+
+
+/* File fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *phile_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ rfc822_header (LOCAL->tmp,LOCAL->env,LOCAL->body);
+ *length = strlen (LOCAL->tmp);
+ return LOCAL->tmp;
+}
+
+
+/* File fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T, always
+ */
+
+long phile_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ SIZEDTEXT *buf = &mail_elt (stream,msgno)->private.special.text;
+ if (!(flags &FT_PEEK)) { /* mark message as seen */
+ mail_elt (stream,msgno)->seen = T;
+ mm_flags (stream,msgno);
+ }
+ INIT (bs,mail_string,buf->data,buf->size);
+ return T;
+}
+
+/* File ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+long phile_ping (MAILSTREAM *stream)
+{
+ return T;
+}
+
+/* File check mailbox
+ * Accepts: MAIL stream
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+void phile_check (MAILSTREAM *stream)
+{
+ mm_log ("Check completed",NIL);
+}
+
+/* File expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T if success, NIL if failure
+ */
+
+long phile_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
+ return LONGT;
+}
+
+/* File copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long phile_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ char tmp[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (tmp,"Can't copy - file \"%s\" is not in valid mailbox format",
+ stream->mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+}
+
+
+/* File append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback function
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long phile_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ char tmp[MAILTMPLEN],file[MAILTMPLEN];
+ char *s = mailboxfile (file,mailbox);
+ if (s && *s)
+ sprintf (tmp,"Can't append - not in valid mailbox format: %.80s",s);
+ else sprintf (tmp,"Can't append - invalid name: %.80s",mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+}
diff --git a/imap/src/osdep/unix/pmatch.c b/imap/src/osdep/unix/pmatch.c
new file mode 100644
index 00000000..a7539e50
--- /dev/null
+++ b/imap/src/osdep/unix/pmatch.c
@@ -0,0 +1,89 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: IMAP Wildcard Matching Routines (case-dependent)
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 June 2000
+ * Last Edited: 30 August 2006
+ */
+
+/* Wildcard pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if pattern matches base, else NIL
+ */
+
+long pmatch_full (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ /* % at end, OK if no inferiors */
+ if (!pat[1]) return (delim && strchr (s,delim)) ? NIL : T;
+ /* scan remainder of string until delimiter */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while ((*s != delim) && *s++);
+ break;
+ case '*': /* match 0 or more characters */
+ if (!pat[1]) return T; /* * at end, unconditional match */
+ /* scan remainder of string */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while (*s++);
+ break;
+ case '\0': /* end of pattern */
+ return *s ? NIL : T; /* success if also end of base */
+ default: /* match this character */
+ return (*pat == *s) ? pmatch_full (s+1,pat+1,delim) : NIL;
+ }
+ return NIL;
+}
+
+/* Directory pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if base is a matching directory of pattern, else NIL
+ */
+
+long dmatch (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ if (!*s) return T; /* end of base means have a subset match */
+ if (!*++pat) return NIL; /* % at end, no inferiors permitted */
+ /* scan remainder of string until delimiter */
+ do if (dmatch (s,pat,delim)) return T;
+ while ((*s != delim) && *s++);
+ if (*s && !s[1]) return T; /* ends with delimiter, must be subset */
+ return dmatch (s,pat,delim);/* do new scan */
+ case '*': /* match 0 or more characters */
+ return T; /* unconditional match */
+ case '\0': /* end of pattern */
+ break;
+ default: /* match this character */
+ if (*s) return (*pat == *s) ? dmatch (s+1,pat+1,delim) : NIL;
+ /* end of base, return if at delimiter */
+ else if (*pat == delim) return T;
+ break;
+ }
+ return NIL;
+}
diff --git a/imap/src/osdep/unix/pseudo.c b/imap/src/osdep/unix/pseudo.c
new file mode 100644
index 00000000..1aae8a53
--- /dev/null
+++ b/imap/src/osdep/unix/pseudo.c
@@ -0,0 +1,36 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Pseudo Header Strings
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 September 1996
+ * Last Edited: 30 August 2006
+ */
+
+/* Local sites may wish to alter this text */
+
+char *pseudo_from = "MAILER-DAEMON";
+char *pseudo_name = "Mail System Internal Data";
+char *pseudo_subject = "DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA";
+char *pseudo_msg =
+ "This text is part of the internal format of your mail folder, and is not\na real message. It is created automatically by the mail system software.\nIf deleted, important folder data will be lost, and it will be re-created\nwith the data reset to initial values."
+ ;
diff --git a/imap/src/osdep/unix/pseudo.h b/imap/src/osdep/unix/pseudo.h
new file mode 100644
index 00000000..c9c07628
--- /dev/null
+++ b/imap/src/osdep/unix/pseudo.h
@@ -0,0 +1,30 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Pseudo Header Strings
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 September 1996
+ * Last Edited: 30 August 2006
+ */
+
+
+extern char *pseudo_from,*pseudo_name,*pseudo_subject,*pseudo_msg;
diff --git a/imap/src/osdep/unix/rename.c b/imap/src/osdep/unix/rename.c
new file mode 100644
index 00000000..6a4c1bda
--- /dev/null
+++ b/imap/src/osdep/unix/rename.c
@@ -0,0 +1,44 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Rename file
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 20 May 1996
+ * Last Edited: 30 August 2006
+ */
+
+/* Emulator for working Unix rename() call
+ * Accepts: old file name
+ * new file name
+ * Returns: 0 if success, -1 if error with error in errno
+ */
+
+int Rename (char *oldname,char *newname)
+{
+ int ret;
+ unlink (newname); /* make sure the old name doesn't exist */
+ /* link to new name, unlink old name */
+ if (!(ret = link (oldname,newname))) unlink (oldname);
+ return ret;
+}
+
+
diff --git a/imap/src/osdep/unix/scandir.c b/imap/src/osdep/unix/scandir.c
new file mode 100644
index 00000000..878343e8
--- /dev/null
+++ b/imap/src/osdep/unix/scandir.c
@@ -0,0 +1,81 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Scan directories
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 15 September 2006
+ */
+
+/* Emulator for BSD scandir() call
+ * Accepts: directory name
+ * destination pointer of names array
+ * selection function
+ * comparison function
+ * Returns: number of elements in the array or -1 if error
+ */
+
+int scandir (char *dirname,struct direct ***namelist,select_t select,
+ compar_t compar)
+{
+ struct direct *p,*d,**names;
+ int nitems;
+ struct stat stb;
+ long nlmax;
+ DIR *dirp = opendir (dirname);/* open directory and get status poop */
+ if ((!dirp) || (fstat (dirp->dd_fd,&stb) < 0)) return -1;
+ nlmax = stb.st_size / 24; /* guesstimate at number of files */
+ names = (struct direct **) fs_get (nlmax * sizeof (struct direct *));
+ nitems = 0; /* initially none found */
+ while (d = readdir (dirp)) { /* read directory item */
+ /* matches select criterion? */
+ if (select && !(*select) (d)) continue;
+ /* get size of direct record for this file */
+ p = (struct direct *) fs_get (DIR_SIZE (d));
+ p->d_ino = d->d_ino; /* copy the poop */
+ strcpy (p->d_name,d->d_name);
+ if (++nitems >= nlmax) { /* if out of space, try bigger guesstimate */
+ void *s = (void *) names; /* stupid language */
+ nlmax *= 2; /* double it */
+ fs_resize ((void **) &s,nlmax * sizeof (struct direct *));
+ names = (struct direct **) s;
+ }
+ names[nitems - 1] = p; /* store this file there */
+ }
+ closedir (dirp); /* done with directory */
+ /* sort if necessary */
+ if (nitems && compar) qsort (names,nitems,sizeof (struct direct *),compar);
+ *namelist = names; /* return directory */
+ return nitems; /* and size */
+}
+
+/* Alphabetic file name comparision
+ * Accepts: first candidate directory entry
+ * second candidate directory entry
+ * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
+ */
+
+int alphasort (void *d1,void *d2)
+{
+ return strcmp ((*(struct direct **) d1)->d_name,
+ (*(struct direct **) d2)->d_name);
+}
diff --git a/imap/src/osdep/unix/setpgrp.c b/imap/src/osdep/unix/setpgrp.c
new file mode 100644
index 00000000..ca957bb2
--- /dev/null
+++ b/imap/src/osdep/unix/setpgrp.c
@@ -0,0 +1,39 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Set process group emulator
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 3 May 1995
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Emulator for BSD setpgrp() call
+ * Accepts: process ID
+ * group ID
+ * Returns: 0 if successful, -1 if failure
+ */
+
+int Setpgrp (int pid,int gid)
+{
+ return setpgrp ();
+}
diff --git a/imap/src/osdep/unix/sig_bsd.c b/imap/src/osdep/unix/sig_bsd.c
new file mode 100644
index 00000000..c615e1b0
--- /dev/null
+++ b/imap/src/osdep/unix/sig_bsd.c
@@ -0,0 +1,40 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: BSD Signals
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 29 April 1997
+ * Last Edited: 30 August 2006
+ */
+
+#include <signal.h>
+
+/* Arm a signal
+ * Accepts: signal number
+ * desired action
+ * Returns: old action
+ */
+
+void *arm_signal (int sig,void *action)
+{
+ return (void *) signal (sig,action);
+}
diff --git a/imap/src/osdep/unix/sig_psx.c b/imap/src/osdep/unix/sig_psx.c
new file mode 100644
index 00000000..0d592c31
--- /dev/null
+++ b/imap/src/osdep/unix/sig_psx.c
@@ -0,0 +1,51 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: POSIX Signals
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 29 April 1997
+ * Last Edited: 30 August 2006
+ */
+
+#include <signal.h>
+#include <string.h>
+
+#ifndef SA_RESTART
+#define SA_RESTART 0
+#endif
+
+/* Arm a signal
+ * Accepts: signal number
+ * desired action
+ * Returns: old action
+ */
+
+void *arm_signal (int sig,void *action)
+{
+ struct sigaction nact,oact;
+ memset (&nact,0,sizeof (struct sigaction));
+ sigemptyset (&nact.sa_mask); /* no signals blocked */
+ nact.sa_handler = action; /* set signal handler */
+ nact.sa_flags = SA_RESTART; /* needed on Linux, nice on SVR4 */
+ sigaction (sig,&nact,&oact); /* do the signal action */
+ return (void *) oact.sa_handler;
+}
diff --git a/imap/src/osdep/unix/sig_sv4.c b/imap/src/osdep/unix/sig_sv4.c
new file mode 100644
index 00000000..599d0b38
--- /dev/null
+++ b/imap/src/osdep/unix/sig_sv4.c
@@ -0,0 +1,40 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: SVR4 Signals
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 29 April 1997
+ * Last Edited: 30 August 2006
+ */
+
+#include <signal.h>
+
+/* Arm a signal
+ * Accepts: signal number
+ * desired action
+ * Returns: old action
+ */
+
+void *arm_signal (int sig,void *action)
+{
+ return (void *) sigset (sig,action);
+}
diff --git a/imap/src/osdep/unix/ssl_none.c b/imap/src/osdep/unix/ssl_none.c
new file mode 100644
index 00000000..e4dedda7
--- /dev/null
+++ b/imap/src/osdep/unix/ssl_none.c
@@ -0,0 +1,141 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy (no SSL) authentication/encryption module
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 7 February 2001
+ * Last Edited: 30 August 2006
+ */
+
+/* Init server for SSL
+ * Accepts: server name
+ */
+
+void ssl_server_init (char *server)
+{
+ syslog (LOG_ERR,"This server does not support SSL");
+ exit (1); /* punt this program too */
+}
+
+
+/* Start TLS
+ * Accepts: /etc/services service name
+ * Returns: cpystr'd error string if TLS failed, else NIL for success
+ */
+
+char *ssl_start_tls (char *server)
+{
+ return cpystr ("This server does not support TLS");
+}
+
+/* Get character
+ * Returns: character or EOF
+ */
+
+int PBIN (void)
+{
+ return getchar ();
+}
+
+
+/* Get string
+ * Accepts: destination string pointer
+ * number of bytes available
+ * Returns: destination string pointer or NIL if EOF
+ */
+
+char *PSIN (char *s,int n)
+{
+ return fgets (s,n,stdin);
+}
+
+
+/* Get record
+ * Accepts: destination string pointer
+ * number of bytes to read
+ * Returns: T if success, NIL otherwise
+ */
+
+long PSINR (char *s,unsigned long n)
+{
+ unsigned long i;
+ while (n && ((i = fread (s,1,n,stdin)) || (errno == EINTR))) s += i,n -= i;
+ return n ? NIL : LONGT;
+}
+
+
+/* Wait for input
+ * Accepts: timeout in seconds
+ * Returns: T if have input on stdin, else NIL
+ */
+
+long INWAIT (long seconds)
+{
+ return server_input_wait (seconds);
+}
+
+/* Put character
+ * Accepts: character
+ * Returns: character written or EOF
+ */
+
+int PBOUT (int c)
+{
+ return putchar (c);
+}
+
+
+/* Put string
+ * Accepts: source string pointer
+ * Returns: 0 or EOF if error
+ */
+
+int PSOUT (char *s)
+{
+ return fputs (s,stdout);
+}
+
+
+/* Put record
+ * Accepts: source sized text
+ * Returns: 0 or EOF if error
+ */
+
+int PSOUTR (SIZEDTEXT *s)
+{
+ unsigned char *t;
+ unsigned long i,j;
+ for (t = s->data,i = s->size;
+ (i && ((j = fwrite (t,1,i,stdout)) || (errno == EINTR)));
+ t += j,i -= j);
+ return i ? EOF : NIL;
+}
+
+
+/* Flush output
+ * Returns: 0 or EOF if error
+ */
+
+int PFLUSH (void)
+{
+ return fflush (stdout);
+}
diff --git a/imap/src/osdep/unix/ssl_unix.c b/imap/src/osdep/unix/ssl_unix.c
new file mode 100644
index 00000000..5a7d0bc0
--- /dev/null
+++ b/imap/src/osdep/unix/ssl_unix.c
@@ -0,0 +1,821 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: SSL authentication/encryption module
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 22 September 1998
+ * Last Edited: 13 January 2007
+ */
+
+#define crypt ssl_private_crypt
+#include <x509v3.h>
+#include <ssl.h>
+#include <err.h>
+#include <pem.h>
+#include <buffer.h>
+#include <bio.h>
+#include <crypto.h>
+#include <rand.h>
+#undef crypt
+
+#define SSLBUFLEN 8192
+#define SSLCIPHERLIST "ALL:!LOW"
+
+
+/* SSL I/O stream */
+
+typedef struct ssl_stream {
+ TCPSTREAM *tcpstream; /* TCP stream */
+ SSL_CTX *context; /* SSL context */
+ SSL *con; /* SSL connection */
+ int ictr; /* input counter */
+ char *iptr; /* input pointer */
+ char ibuf[SSLBUFLEN]; /* input buffer */
+} SSLSTREAM;
+
+#include "sslio.h"
+
+/* Function prototypes */
+
+static SSLSTREAM *ssl_start(TCPSTREAM *tstream,char *host,unsigned long flags);
+static char *ssl_start_work (SSLSTREAM *stream,char *host,unsigned long flags);
+static int ssl_open_verify (int ok,X509_STORE_CTX *ctx);
+static char *ssl_validate_cert (X509 *cert,char *host);
+static long ssl_compare_hostnames (unsigned char *s,unsigned char *pat);
+static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
+ long *contd);
+static long ssl_abort (SSLSTREAM *stream);
+static RSA *ssl_genkey (SSL *con,int export,int keylength);
+
+
+/* Secure Sockets Layer network driver dispatch */
+
+static struct ssl_driver ssldriver = {
+ ssl_open, /* open connection */
+ ssl_aopen, /* open preauthenticated connection */
+ ssl_getline, /* get a line */
+ ssl_getbuffer, /* get a buffer */
+ ssl_soutr, /* output pushed data */
+ ssl_sout, /* output string */
+ ssl_close, /* close connection */
+ ssl_host, /* return host name */
+ ssl_remotehost, /* return remote host name */
+ ssl_port, /* return port number */
+ ssl_localhost /* return local host name */
+};
+ /* non-NIL if doing SSL primary I/O */
+static SSLSTDIOSTREAM *sslstdio = NIL;
+static char *start_tls = NIL; /* non-NIL if start TLS requested */
+
+/* One-time SSL initialization */
+
+static int sslonceonly = 0;
+
+void ssl_onceonlyinit (void)
+{
+ if (!sslonceonly++) { /* only need to call it once */
+ int fd;
+ char tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* if system doesn't have /dev/urandom */
+ if (stat ("/dev/urandom",&sbuf)) {
+ while ((fd = open (tmpnam (tmp),O_WRONLY|O_CREAT|O_EXCL,0600)) < 0)
+ sleep (1);
+ unlink (tmp); /* don't need the file */
+ fstat (fd,&sbuf); /* get information about the file */
+ close (fd); /* flush descriptor */
+ /* not great but it'll have to do */
+ sprintf (tmp + strlen (tmp),"%.80s%lx%.80s%lx%lx%lx%lx%lx",
+ tcp_serveraddr (),(unsigned long) tcp_serverport (),
+ tcp_clientaddr (),(unsigned long) tcp_clientport (),
+ (unsigned long) sbuf.st_ino,(unsigned long) time (0),
+ (unsigned long) gethostid (),(unsigned long) getpid ());
+ RAND_seed (tmp,strlen (tmp));
+ }
+ /* apply runtime linkage */
+ mail_parameters (NIL,SET_SSLDRIVER,(void *) &ssldriver);
+ mail_parameters (NIL,SET_SSLSTART,(void *) ssl_start);
+ SSL_library_init (); /* add all algorithms */
+ }
+}
+
+/* SSL open
+ * Accepts: host name
+ * contact service name
+ * contact port number
+ * Returns: SSL stream if success else NIL
+ */
+
+SSLSTREAM *ssl_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = tcp_open (host,service,port);
+ return stream ? ssl_start (stream,host,port) : NIL;
+}
+
+
+/* SSL authenticated open
+ * Accepts: host name
+ * service name
+ * returned user name buffer
+ * Returns: SSL stream if success else NIL
+ */
+
+SSLSTREAM *ssl_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL; /* don't use this mechanism with SSL */
+}
+
+/* Start SSL/TLS negotiations
+ * Accepts: open TCP stream of session
+ * user's host name
+ * flags
+ * Returns: SSL stream if success else NIL
+ */
+
+static SSLSTREAM *ssl_start (TCPSTREAM *tstream,char *host,unsigned long flags)
+{
+ char *reason,tmp[MAILTMPLEN];
+ sslfailure_t sf = (sslfailure_t) mail_parameters (NIL,GET_SSLFAILURE,NIL);
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ void *data = (*bn) (BLOCK_SENSITIVE,NIL);
+ SSLSTREAM *stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
+ sizeof (SSLSTREAM));
+ stream->tcpstream = tstream; /* bind TCP stream */
+ /* do the work */
+ reason = ssl_start_work (stream,host,flags);
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ if (reason) { /* failed? */
+ ssl_close (stream); /* failed to do SSL */
+ stream = NIL; /* no stream returned */
+ switch (*reason) { /* analyze reason */
+ case '*': /* certificate failure */
+ ++reason; /* skip over certificate failure indication */
+ /* pass to error callback */
+ if (sf) (*sf) (host,reason,flags);
+ else { /* no error callback, build error message */
+ sprintf (tmp,"Certificate failure for %.80s: %.512s",host,reason);
+ mm_log (tmp,ERROR);
+ }
+ case '\0': /* user answered no to certificate callback */
+ if (flags & NET_TRYSSL) /* return dummy stream to stop tryssl */
+ stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
+ sizeof (SSLSTREAM));
+ break;
+ default: /* non-certificate failure */
+ if (flags & NET_TRYSSL); /* no error output if tryssl */
+ /* pass to error callback */
+ else if (sf) (*sf) (host,reason,flags);
+ else { /* no error callback, build error message */
+ sprintf (tmp,"TLS/SSL failure for %.80s: %.512s",host,reason);
+ mm_log (tmp,ERROR);
+ }
+ break;
+ }
+ }
+ return stream;
+}
+
+/* Start SSL/TLS negotiations worker routine
+ * Accepts: SSL stream
+ * user's host name
+ * flags
+ * Returns: NIL if success, else error reason
+ */
+
+ /* evil but I had no choice */
+static char *ssl_last_error = NIL;
+static char *ssl_last_host = NIL;
+
+static char *ssl_start_work (SSLSTREAM *stream,char *host,unsigned long flags)
+{
+ BIO *bio;
+ X509 *cert;
+ unsigned long sl,tl;
+ char *s,*t,*err,tmp[MAILTMPLEN];
+ sslcertificatequery_t scq =
+ (sslcertificatequery_t) mail_parameters (NIL,GET_SSLCERTIFICATEQUERY,NIL);
+ sslclientcert_t scc =
+ (sslclientcert_t) mail_parameters (NIL,GET_SSLCLIENTCERT,NIL);
+ sslclientkey_t sck =
+ (sslclientkey_t) mail_parameters (NIL,GET_SSLCLIENTKEY,NIL);
+ if (ssl_last_error) fs_give ((void **) &ssl_last_error);
+ ssl_last_host = host;
+ if (!(stream->context = SSL_CTX_new ((flags & NET_TLSCLIENT) ?
+ TLSv1_client_method () :
+ SSLv23_client_method ())))
+ return "SSL context failed";
+ SSL_CTX_set_options (stream->context,0);
+ /* disable certificate validation? */
+ if (flags & NET_NOVALIDATECERT)
+ SSL_CTX_set_verify (stream->context,SSL_VERIFY_NONE,NIL);
+ else SSL_CTX_set_verify (stream->context,SSL_VERIFY_PEER,ssl_open_verify);
+ /* set default paths to CAs... */
+ SSL_CTX_set_default_verify_paths (stream->context);
+ /* ...unless a non-standard path desired */
+ if (s = (char *) mail_parameters (NIL,GET_SSLCAPATH,NIL))
+ SSL_CTX_load_verify_locations (stream->context,NIL,s);
+ /* want to send client certificate? */
+ if (scc && (s = (*scc) ()) && (sl = strlen (s))) {
+ if (cert = PEM_read_bio_X509 (bio = BIO_new_mem_buf (s,sl),NIL,NIL,NIL)) {
+ SSL_CTX_use_certificate (stream->context,cert);
+ X509_free (cert);
+ }
+ BIO_free (bio);
+ if (!cert) return "SSL client certificate failed";
+ /* want to supply private key? */
+ if ((t = (sck ? (*sck) () : s)) && (tl = strlen (t))) {
+ EVP_PKEY *key;
+ if (key = PEM_read_bio_PrivateKey (bio = BIO_new_mem_buf (t,tl),
+ NIL,NIL,"")) {
+ SSL_CTX_use_PrivateKey (stream->context,key);
+ EVP_PKEY_free (key);
+ }
+ BIO_free (bio);
+ memset (t,0,tl); /* erase key */
+ }
+ if (s != t) memset (s,0,sl);/* erase certificate if different from key */
+ }
+
+ /* create connection */
+ if (!(stream->con = (SSL *) SSL_new (stream->context)))
+ return "SSL connection failed";
+ bio = BIO_new_socket (stream->tcpstream->tcpsi,BIO_NOCLOSE);
+ SSL_set_bio (stream->con,bio,bio);
+ SSL_set_connect_state (stream->con);
+ if (SSL_in_init (stream->con)) SSL_total_renegotiations (stream->con);
+ /* now negotiate SSL */
+ if (SSL_write (stream->con,"",0) < 0)
+ return ssl_last_error ? ssl_last_error : "SSL negotiation failed";
+ /* need to validate host names? */
+ if (!(flags & NET_NOVALIDATECERT) &&
+ (err = ssl_validate_cert (cert = SSL_get_peer_certificate (stream->con),
+ host))) {
+ /* application callback */
+ if (scq) return (*scq) (err,host,cert ? cert->name : "???") ? NIL : "";
+ /* error message to return via mm_log() */
+ sprintf (tmp,"*%.128s: %.255s",err,cert ? cert->name : "???");
+ return ssl_last_error = cpystr (tmp);
+ }
+ return NIL;
+}
+
+/* SSL certificate verification callback
+ * Accepts: error flag
+ * X509 context
+ * Returns: error flag
+ */
+
+static int ssl_open_verify (int ok,X509_STORE_CTX *ctx)
+{
+ char *err,cert[256],tmp[MAILTMPLEN];
+ sslcertificatequery_t scq =
+ (sslcertificatequery_t) mail_parameters (NIL,GET_SSLCERTIFICATEQUERY,NIL);
+ if (!ok) { /* in case failure */
+ err = (char *) X509_verify_cert_error_string
+ (X509_STORE_CTX_get_error (ctx));
+ X509_NAME_oneline (X509_get_subject_name
+ (X509_STORE_CTX_get_current_cert (ctx)),cert,255);
+ if (!scq) { /* mm_log() error message if no callback */
+ sprintf (tmp,"*%.128s: %.255s",err,cert);
+ ssl_last_error = cpystr (tmp);
+ }
+ /* ignore error if application says to */
+ else if ((*scq) (err,ssl_last_host,cert)) ok = T;
+ /* application wants punt */
+ else ssl_last_error = cpystr ("");
+ }
+ return ok;
+}
+
+
+/* SSL validate certificate
+ * Accepts: certificate
+ * host to validate against
+ * Returns: NIL if validated, else string of error message
+ */
+
+static char *ssl_validate_cert (X509 *cert,char *host)
+{
+ int i,n;
+ char *s,*t,*ret;
+ void *ext;
+ GENERAL_NAME *name;
+ /* make sure have a certificate */
+ if (!cert) ret = "No certificate from server";
+ /* and that it has a name */
+ else if (!cert->name) ret = "No name in certificate";
+ /* locate CN */
+ else if (s = strstr (cert->name,"/CN=")) {
+ if (t = strchr (s += 4,'/')) *t = '\0';
+ /* host name matches pattern? */
+ ret = ssl_compare_hostnames (host,s) ? NIL :
+ "Server name does not match certificate";
+ if (t) *t = '/'; /* restore smashed delimiter */
+ /* if mismatch, see if in extensions */
+ if (ret && (ext = X509_get_ext_d2i (cert,NID_subject_alt_name,NIL,NIL)) &&
+ (n = sk_GENERAL_NAME_num (ext)))
+ /* older versions of OpenSSL use "ia5" instead of dNSName */
+ for (i = 0; ret && (i < n); i++)
+ if ((name = sk_GENERAL_NAME_value (ext,i)) &&
+ (name->type = GEN_DNS) && (s = name->d.ia5->data) &&
+ ssl_compare_hostnames (host,s)) ret = NIL;
+ }
+ else ret = "Unable to locate common name in certificate";
+ return ret;
+}
+
+/* Case-independent wildcard pattern match
+ * Accepts: base string
+ * pattern string
+ * Returns: T if pattern matches base, else NIL
+ */
+
+static long ssl_compare_hostnames (unsigned char *s,unsigned char *pat)
+{
+ long ret = NIL;
+ switch (*pat) {
+ case '*': /* wildcard */
+ if (pat[1]) { /* there must be a pattern suffix */
+ /* there is, scan base against it */
+ do if (ssl_compare_hostnames (s,pat+1)) ret = LONGT;
+ while (!ret && (*s != '.') && *s++);
+ }
+ break;
+ case '\0': /* end of pattern */
+ if (!*s) ret = LONGT; /* success if base is also at end */
+ break;
+ default: /* non-wildcard, recurse if match */
+ if (!compare_uchar (*pat,*s)) ret = ssl_compare_hostnames (s+1,pat+1);
+ break;
+ }
+ return ret;
+}
+
+/* SSL receive line
+ * Accepts: SSL stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *ssl_getline (SSLSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = ssl_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = ssl_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* SSL receive line or partial line
+ * Accepts: SSL stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!ssl_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!ssl_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* SSL receive buffer
+ * Accepts: SSL stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long ssl_getbuffer (SSLSTREAM *stream,unsigned long size,char *buffer)
+{
+ unsigned long n;
+ while (size > 0) { /* until request satisfied */
+ if (!ssl_getdata (stream)) return NIL;
+ n = min (size,stream->ictr);/* number of bytes to transfer */
+ /* do the copy */
+ memcpy (buffer,stream->iptr,n);
+ buffer += n; /* update pointer */
+ stream->iptr += n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -= n;
+ }
+ buffer[0] = '\0'; /* tie off string */
+ return T;
+}
+
+/* SSL receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long ssl_getdata (SSLSTREAM *stream)
+{
+ int i,sock;
+ fd_set fds,efds;
+ struct timeval tmo;
+ tcptimeout_t tmoh = (tcptimeout_t) mail_parameters (NIL,GET_TIMEOUT,NIL);
+ long ttmo_read = (long) mail_parameters (NIL,GET_READTIMEOUT,NIL);
+ time_t t = time (0);
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (!stream->con || ((sock = SSL_get_fd (stream->con)) < 0)) return NIL;
+ /* tcp_unix should have prevented this */
+ if (sock >= FD_SETSIZE) fatal ("unselectable socket in ssl_getdata()");
+ (*bn) (BLOCK_TCPREAD,NIL);
+ while (stream->ictr < 1) { /* if nothing in the buffer */
+ time_t tl = time (0); /* start of request */
+ time_t now = tl;
+ int ti = ttmo_read ? now + ttmo_read : 0;
+ if (SSL_pending (stream->con)) i = 1;
+ else {
+ if (tcpdebug) mm_log ("Reading SSL data",TCPDEBUG);
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ FD_SET (sock,&fds); /* set bit in selection vector */
+ FD_SET (sock,&efds); /* set bit in error selection vector */
+ errno = NIL; /* block and read */
+ do { /* block under timeout */
+ tmo.tv_sec = ti ? ti - now : 0;
+ i = select (sock+1,&fds,0,&efds,ti ? &tmo : 0);
+ now = time (0); /* fake timeout if interrupt & time expired */
+ if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0;
+ } while ((i < 0) && (errno == EINTR));
+ }
+ if (i) { /* non-timeout result from select? */
+ errno = 0; /* just in case */
+ if (i > 0) /* read what we can */
+ while (((i = SSL_read (stream->con,stream->ibuf,SSLBUFLEN)) < 0) &&
+ ((errno == EINTR) ||
+ (SSL_get_error (stream->con,i) == SSL_ERROR_WANT_READ)));
+ if (i <= 0) { /* error seen? */
+ if (tcpdebug) {
+ char *s,tmp[MAILTMPLEN];
+ if (i) sprintf (s = tmp,"SSL data read I/O error %d SSL error %d",
+ errno,SSL_get_error (stream->con,i));
+ else s = "SSL data read end of file";
+ mm_log (s,TCPDEBUG);
+ }
+ return ssl_abort (stream);
+ }
+ stream->iptr = stream->ibuf;/* point at TCP buffer */
+ stream->ictr = i; /* set new byte count */
+ if (tcpdebug) mm_log ("Successfully read SSL data",TCPDEBUG);
+ }
+ /* timeout, punt unless told not to */
+ else if (!tmoh || !(*tmoh) (now - t,now - tl, stream->tcpstream->host)) {
+ if (tcpdebug) mm_log ("SSL data read timeout",TCPDEBUG);
+ return ssl_abort (stream);
+ }
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return T;
+}
+
+/* SSL send string as record
+ * Accepts: SSL stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long ssl_soutr (SSLSTREAM *stream,char *string)
+{
+ return ssl_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* SSL send string
+ * Accepts: SSL stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long ssl_sout (SSLSTREAM *stream,char *string,unsigned long size)
+{
+ long i;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (!stream->con) return NIL;
+ (*bn) (BLOCK_TCPWRITE,NIL);
+ if (tcpdebug) mm_log ("Writing to SSL",TCPDEBUG);
+ /* until request satisfied */
+ for (i = 0; size > 0; string += i,size -= i)
+ /* write as much as we can */
+ if ((i = SSL_write (stream->con,string,(int) min (SSLBUFLEN,size))) < 0) {
+ if (tcpdebug) {
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"SSL data write I/O error %d SSL error %d",
+ errno,SSL_get_error (stream->con,i));
+ mm_log (tmp,TCPDEBUG);
+ }
+ return ssl_abort (stream);/* write failed */
+ }
+ if (tcpdebug) mm_log ("successfully wrote to TCP",TCPDEBUG);
+ (*bn) (BLOCK_NONE,NIL);
+ return LONGT; /* all done */
+}
+
+/* SSL close
+ * Accepts: SSL stream
+ */
+
+void ssl_close (SSLSTREAM *stream)
+{
+ ssl_abort (stream); /* nuke the stream */
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* SSL abort stream
+ * Accepts: SSL stream
+ * Returns: NIL always
+ */
+
+static long ssl_abort (SSLSTREAM *stream)
+{
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (stream->con) { /* close SSL connection */
+ SSL_shutdown (stream->con);
+ SSL_free (stream->con);
+ stream->con = NIL;
+ }
+ if (stream->context) { /* clean up context */
+ SSL_CTX_free (stream->context);
+ stream->context = NIL;
+ }
+ if (stream->tcpstream) { /* close TCP stream */
+ tcp_close (stream->tcpstream);
+ stream->tcpstream = NIL;
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return NIL;
+}
+
+/* SSL get host name
+ * Accepts: SSL stream
+ * Returns: host name for this stream
+ */
+
+char *ssl_host (SSLSTREAM *stream)
+{
+ return tcp_host (stream->tcpstream);
+}
+
+
+/* SSL get remote host name
+ * Accepts: SSL stream
+ * Returns: host name for this stream
+ */
+
+char *ssl_remotehost (SSLSTREAM *stream)
+{
+ return tcp_remotehost (stream->tcpstream);
+}
+
+
+/* SSL return port for this stream
+ * Accepts: SSL stream
+ * Returns: port number for this stream
+ */
+
+unsigned long ssl_port (SSLSTREAM *stream)
+{
+ return tcp_port (stream->tcpstream);
+}
+
+
+/* SSL get local host name
+ * Accepts: SSL stream
+ * Returns: local host name
+ */
+
+char *ssl_localhost (SSLSTREAM *stream)
+{
+ return tcp_localhost (stream->tcpstream);
+}
+
+/* Start TLS
+ * Accepts: /etc/services service name
+ * Returns: cpystr'd error string if TLS failed, else NIL for success
+ */
+
+char *ssl_start_tls (char *server)
+{
+ char tmp[MAILTMPLEN];
+ struct stat sbuf;
+ if (sslstdio) return cpystr ("Already in an SSL session");
+ if (start_tls) return cpystr ("TLS already started");
+ if (server) { /* build specific certificate/key file name */
+ sprintf (tmp,"%s/%s-%s.pem",SSL_CERT_DIRECTORY,server,tcp_serveraddr ());
+ if (stat (tmp,&sbuf)) { /* use non-specific name if no specific file */
+ sprintf (tmp,"%s/%s.pem",SSL_CERT_DIRECTORY,server);
+ if (stat (tmp,&sbuf)) return cpystr ("Server certificate not installed");
+ }
+ start_tls = server; /* switch to STARTTLS mode */
+ }
+ return NIL;
+}
+
+/* Init server for SSL
+ * Accepts: server name
+ */
+
+void ssl_server_init (char *server)
+{
+ char cert[MAILTMPLEN],key[MAILTMPLEN];
+ unsigned long i;
+ struct stat sbuf;
+ SSLSTREAM *stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
+ sizeof (SSLSTREAM));
+ ssl_onceonlyinit (); /* make sure algorithms added */
+ ERR_load_crypto_strings ();
+ SSL_load_error_strings ();
+ /* build specific certificate/key file names */
+ sprintf (cert,"%s/%s-%s.pem",SSL_CERT_DIRECTORY,server,tcp_serveraddr ());
+ sprintf (key,"%s/%s-%s.pem",SSL_KEY_DIRECTORY,server,tcp_serveraddr ());
+ /* use non-specific name if no specific cert */
+ if (stat (cert,&sbuf)) sprintf (cert,"%s/%s.pem",SSL_CERT_DIRECTORY,server);
+ if (stat (key,&sbuf)) { /* use non-specific name if no specific key */
+ sprintf (key,"%s/%s.pem",SSL_KEY_DIRECTORY,server);
+ /* use cert file as fallback for key */
+ if (stat (key,&sbuf)) strcpy (key,cert);
+ }
+ /* create context */
+ if (!(stream->context = SSL_CTX_new (start_tls ?
+ TLSv1_server_method () :
+ SSLv23_server_method ())))
+ syslog (LOG_ALERT,"Unable to create SSL context, host=%.80s",
+ tcp_clienthost ());
+ else { /* set context options */
+ SSL_CTX_set_options (stream->context,SSL_OP_ALL);
+ /* set cipher list */
+ if (!SSL_CTX_set_cipher_list (stream->context,SSLCIPHERLIST))
+ syslog (LOG_ALERT,"Unable to set cipher list %.80s, host=%.80s",
+ SSLCIPHERLIST,tcp_clienthost ());
+ /* load certificate */
+ else if (!SSL_CTX_use_certificate_chain_file (stream->context,cert))
+ syslog (LOG_ALERT,"Unable to load certificate from %.80s, host=%.80s",
+ cert,tcp_clienthost ());
+ /* load key */
+ else if (!(SSL_CTX_use_RSAPrivateKey_file (stream->context,key,
+ SSL_FILETYPE_PEM)))
+ syslog (LOG_ALERT,"Unable to load private key from %.80s, host=%.80s",
+ key,tcp_clienthost ());
+
+ else { /* generate key if needed */
+ if (SSL_CTX_need_tmp_RSA (stream->context))
+ SSL_CTX_set_tmp_rsa_callback (stream->context,ssl_genkey);
+ /* create new SSL connection */
+ if (!(stream->con = SSL_new (stream->context)))
+ syslog (LOG_ALERT,"Unable to create SSL connection, host=%.80s",
+ tcp_clienthost ());
+ else { /* set file descriptor */
+ SSL_set_fd (stream->con,0);
+ /* all OK if accepted */
+ if (SSL_accept (stream->con) < 0)
+ syslog (LOG_INFO,"Unable to accept SSL connection, host=%.80s",
+ tcp_clienthost ());
+ else { /* server set up */
+ sslstdio = (SSLSTDIOSTREAM *)
+ memset (fs_get (sizeof(SSLSTDIOSTREAM)),0,sizeof (SSLSTDIOSTREAM));
+ sslstdio->sslstream = stream;
+ /* available space in output buffer */
+ sslstdio->octr = SSLBUFLEN;
+ /* current output buffer pointer */
+ sslstdio->optr = sslstdio->obuf;
+ /* allow plaintext if disable value was 2 */
+ if ((long) mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL) > 1)
+ mail_parameters (NIL,SET_DISABLEPLAINTEXT,NIL);
+ /* unhide PLAIN SASL authenticator */
+ mail_parameters (NIL,UNHIDE_AUTHENTICATOR,"PLAIN");
+ mail_parameters (NIL,UNHIDE_AUTHENTICATOR,"LOGIN");
+ return;
+ }
+ }
+ }
+ }
+ while (i = ERR_get_error ()) /* SSL failure */
+ syslog (LOG_ERR,"SSL error status: %.80s",ERR_error_string (i,NIL));
+ ssl_close (stream); /* punt stream */
+ exit (1); /* punt this program too */
+}
+
+/* Generate one-time key for server
+ * Accepts: SSL connection
+ * export flag
+ * keylength
+ * Returns: generated key, always
+ */
+
+static RSA *ssl_genkey (SSL *con,int export,int keylength)
+{
+ unsigned long i;
+ static RSA *key = NIL;
+ if (!key) { /* if don't have a key already */
+ /* generate key */
+ if (!(key = RSA_generate_key (export ? keylength : 1024,RSA_F4,NIL,NIL))) {
+ syslog (LOG_ALERT,"Unable to generate temp key, host=%.80s",
+ tcp_clienthost ());
+ while (i = ERR_get_error ())
+ syslog (LOG_ALERT,"SSL error status: %s",ERR_error_string (i,NIL));
+ exit (1);
+ }
+ }
+ return key;
+}
+
+/* Wait for stdin input
+ * Accepts: timeout in seconds
+ * Returns: T if have input on stdin, else NIL
+ */
+
+long ssl_server_input_wait (long seconds)
+{
+ int i,sock;
+ fd_set fds,efd;
+ struct timeval tmo;
+ SSLSTREAM *stream;
+ if (!sslstdio) return server_input_wait (seconds);
+ /* input available in buffer */
+ if (((stream = sslstdio->sslstream)->ictr > 0) ||
+ !stream->con || ((sock = SSL_get_fd (stream->con)) < 0)) return LONGT;
+ /* sock ought to be 0 always */
+ if (sock >= FD_SETSIZE) fatal ("unselectable socket in ssl_getdata()");
+ /* input available from SSL */
+ if (SSL_pending (stream->con) &&
+ ((i = SSL_read (stream->con,stream->ibuf,SSLBUFLEN)) > 0)) {
+ stream->iptr = stream->ibuf;/* point at TCP buffer */
+ stream->ictr = i; /* set new byte count */
+ return LONGT;
+ }
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efd); /* initialize selection vector */
+ FD_SET (sock,&fds); /* set bit in selection vector */
+ FD_SET (sock,&efd); /* set bit in selection vector */
+ tmo.tv_sec = seconds; tmo.tv_usec = 0;
+ /* see if input available from the socket */
+ return select (sock+1,&fds,0,&efd,&tmo) ? LONGT : NIL;
+}
+
+#include "sslstdio.c"
diff --git a/imap/src/osdep/unix/sslstdio.c b/imap/src/osdep/unix/sslstdio.c
new file mode 100644
index 00000000..4059a859
--- /dev/null
+++ b/imap/src/osdep/unix/sslstdio.c
@@ -0,0 +1,169 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: SSL standard I/O routines for server use
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 22 September 1998
+ * Last Edited: 30 August 2006
+ */
+
+/* Get character
+ * Returns: character or EOF
+ */
+
+int PBIN (void)
+{
+ if (!sslstdio) return getchar ();
+ if (!ssl_getdata (sslstdio->sslstream)) return EOF;
+ /* one last byte available */
+ sslstdio->sslstream->ictr--;
+ return (int) *(sslstdio->sslstream->iptr)++;
+}
+
+
+/* Get string
+ * Accepts: destination string pointer
+ * number of bytes available
+ * Returns: destination string pointer or NIL if EOF
+ */
+
+char *PSIN (char *s,int n)
+{
+ int i,c;
+ if (start_tls) { /* doing a start TLS? */
+ ssl_server_init (start_tls);/* enter the mode */
+ start_tls = NIL; /* don't do this again */
+ }
+ if (!sslstdio) return fgets (s,n,stdin);
+ for (i = c = 0, n-- ; (c != '\n') && (i < n); sslstdio->sslstream->ictr--) {
+ if ((sslstdio->sslstream->ictr <= 0) && !ssl_getdata (sslstdio->sslstream))
+ return NIL; /* read error */
+ c = s[i++] = *(sslstdio->sslstream->iptr)++;
+ }
+ s[i] = '\0'; /* tie off string */
+ return s;
+}
+
+
+/* Get record
+ * Accepts: destination string pointer
+ * number of bytes to read
+ * Returns: T if success, NIL otherwise
+ */
+
+long PSINR (char *s,unsigned long n)
+{
+ unsigned long i;
+ if (start_tls) { /* doing a start TLS? */
+ ssl_server_init (start_tls);/* enter the mode */
+ start_tls = NIL; /* don't do this again */
+ }
+ if (sslstdio) return ssl_getbuffer (sslstdio->sslstream,n,s);
+ /* non-SSL case */
+ while (n && ((i = fread (s,1,n,stdin)) || (errno == EINTR))) s += i,n -= i;
+ return n ? NIL : LONGT;
+}
+
+
+/* Wait for stdin input
+ * Accepts: timeout in seconds
+ * Returns: T if have input on stdin, else NIL
+ */
+
+long INWAIT (long seconds)
+{
+ return (sslstdio ? ssl_server_input_wait : server_input_wait) (seconds);
+}
+
+/* Put character
+ * Accepts: character
+ * Returns: character written or EOF
+ */
+
+int PBOUT (int c)
+{
+ if (!sslstdio) return putchar (c);
+ /* flush buffer if full */
+ if (!sslstdio->octr && PFLUSH ()) return EOF;
+ sslstdio->octr--; /* count down one character */
+ *sslstdio->optr++ = c; /* write character */
+ return c; /* return that character */
+}
+
+
+/* Put string
+ * Accepts: destination string pointer
+ * Returns: 0 or EOF if error
+ */
+
+int PSOUT (char *s)
+{
+ if (!sslstdio) return fputs (s,stdout);
+ while (*s) { /* flush buffer if full */
+ if (!sslstdio->octr && PFLUSH ()) return EOF;
+ *sslstdio->optr++ = *s++; /* write one more character */
+ sslstdio->octr--; /* count down one character */
+ }
+ return 0; /* success */
+}
+
+/* Put record
+ * Accepts: source sized text
+ * Returns: 0 or EOF if error
+ */
+
+int PSOUTR (SIZEDTEXT *s)
+{
+ unsigned char *t = s->data;
+ unsigned long i = s->size;
+ unsigned long j;
+ if (sslstdio) while (i) { /* until request satisfied */
+ /* flush buffer if full */
+ if (!sslstdio->octr && PFLUSH ()) break;
+ /* blat as big a chucnk as we can */
+ memcpy (sslstdio->optr,t,j = min (i,sslstdio->octr));
+ sslstdio->optr += j; /* account for chunk */
+ sslstdio->octr -= j;
+ t += j;
+ i -= j;
+ }
+ else while (i && ((j = fwrite (t,1,i,stdout)) || (errno == EINTR)))
+ t += j,i -= j;
+ return i ? EOF : NIL;
+}
+
+
+/* Flush output
+ * Returns: 0 or EOF if error
+ */
+
+int PFLUSH (void)
+{
+ if (!sslstdio) return fflush (stdout);
+ /* force out buffer */
+ if (!ssl_sout (sslstdio->sslstream,sslstdio->obuf,
+ SSLBUFLEN - sslstdio->octr)) return EOF;
+ /* renew output buffer */
+ sslstdio->optr = sslstdio->obuf;
+ sslstdio->octr = SSLBUFLEN;
+ return 0; /* success */
+}
diff --git a/imap/src/osdep/unix/strerror.c b/imap/src/osdep/unix/strerror.c
new file mode 100644
index 00000000..3c2e6a14
--- /dev/null
+++ b/imap/src/osdep/unix/strerror.c
@@ -0,0 +1,37 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Error number to string
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Return implementation-defined string corresponding to error
+ * Accepts: error number
+ * Returns: string for that error
+ */
+
+char *strerror (int n)
+{
+ return (n >= 0 && n < sys_nerr) ? sys_errlist[n] : NIL;
+}
diff --git a/imap/src/osdep/unix/tcp_unix.c b/imap/src/osdep/unix/tcp_unix.c
new file mode 100644
index 00000000..127cf2ae
--- /dev/null
+++ b/imap/src/osdep/unix/tcp_unix.c
@@ -0,0 +1,1043 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 13 January 2008
+ */
+
+#include "ip_unix.c"
+
+#undef write /* don't use redefined write() */
+
+static tcptimeout_t tmoh = NIL; /* TCP timeout handler routine */
+static long ttmo_open = 0; /* TCP timeouts, in seconds */
+static long ttmo_read = 0;
+static long ttmo_write = 0;
+static long rshtimeout = 15; /* rsh timeout */
+static char *rshcommand = NIL; /* rsh command */
+static char *rshpath = NIL; /* rsh path */
+static long sshtimeout = 15; /* ssh timeout */
+static char *sshcommand = NIL; /* ssh command */
+static char *sshpath = NIL; /* ssh path */
+static long allowreversedns = T;/* allow reverse DNS lookup */
+static long tcpdebug = NIL; /* extra TCP debugging telemetry */
+static char *myClientAddr = NIL;/* client IP address */
+static char *myClientHost = NIL;/* client DNS name */
+static long myClientPort = -1; /* client port number */
+static char *myServerAddr = NIL;/* server IP address */
+static char *myServerHost = NIL;/* server DNS name */
+static long myServerPort = -1; /* server port number */
+
+extern long maxposint; /* get this from write.c */
+
+/* Local function prototypes */
+
+int tcp_socket_open (int family,void *adr,size_t adrlen,unsigned short port,
+ char *tmp,int *ctr,char *hst);
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd);
+long tcp_abort (TCPSTREAM *stream);
+char *tcp_name (struct sockaddr *sadr,long flag);
+char *tcp_name_valid (char *s);
+
+/* TCP/IP manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tcp_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_TIMEOUT:
+ tmoh = (tcptimeout_t) value;
+ case GET_TIMEOUT:
+ ret = (void *) tmoh;
+ break;
+ case SET_OPENTIMEOUT:
+ ttmo_open = (long) value;
+ case GET_OPENTIMEOUT:
+ ret = (void *) ttmo_open;
+ break;
+ case SET_READTIMEOUT:
+ ttmo_read = (long) value;
+ case GET_READTIMEOUT:
+ ret = (void *) ttmo_read;
+ break;
+ case SET_WRITETIMEOUT:
+ ttmo_write = (long) value;
+ case GET_WRITETIMEOUT:
+ ret = (void *) ttmo_write;
+ break;
+ case SET_ALLOWREVERSEDNS:
+ allowreversedns = (long) value;
+ case GET_ALLOWREVERSEDNS:
+ ret = (void *) allowreversedns;
+ break;
+ case SET_TCPDEBUG:
+ tcpdebug = (long) value;
+ case GET_TCPDEBUG:
+ ret = (void *) tcpdebug;
+ break;
+
+ case SET_RSHTIMEOUT:
+ rshtimeout = (long) value;
+ case GET_RSHTIMEOUT:
+ ret = (void *) rshtimeout;
+ break;
+ case SET_RSHCOMMAND:
+ if (rshcommand) fs_give ((void **) &rshcommand);
+ rshcommand = cpystr ((char *) value);
+ case GET_RSHCOMMAND:
+ ret = (void *) rshcommand;
+ break;
+ case SET_RSHPATH:
+ if (rshpath) fs_give ((void **) &rshpath);
+ rshpath = cpystr ((char *) value);
+ case GET_RSHPATH:
+ ret = (void *) rshpath;
+ break;
+ case SET_SSHTIMEOUT:
+ sshtimeout = (long) value;
+ case GET_SSHTIMEOUT:
+ ret = (void *) sshtimeout;
+ break;
+ case SET_SSHCOMMAND:
+ if (sshcommand) fs_give ((void **) &sshcommand);
+ sshcommand = cpystr ((char *) value);
+ case GET_SSHCOMMAND:
+ ret = (void *) sshcommand;
+ break;
+ case SET_SSHPATH:
+ if (sshpath) fs_give ((void **) &sshpath);
+ sshpath = cpystr ((char *) value);
+ case GET_SSHPATH:
+ ret = (void *) sshpath;
+ break;
+ }
+ return ret;
+}
+
+/* TCP/IP open
+ * Accepts: host name
+ * contact service name
+ * contact port number and optional silent flag
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = NIL;
+ int family;
+ int sock = -1;
+ int ctr = 0;
+ int silent = (port & NET_SILENT) ? T : NIL;
+ int *ctrp = (port & NET_NOOPENTIMEOUT) ? NIL : &ctr;
+ char *s,*hostname,tmp[MAILTMPLEN];
+ void *adr;
+ size_t adrlen;
+ struct servent *sv = NIL;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ void *data,*next;
+ port &= 0xffff; /* erase flags */
+ /* lookup service */
+ if (service && (sv = getservbyname (service,"tcp")))
+ port = ntohs (sv->s_port);
+ /* The domain literal form is used (rather than simply the dotted decimal
+ as with other Unix programs) because it has to be a valid "host name"
+ in mailsystem terminology. */
+ /* look like domain literal? */
+ if (host[0] == '[' && host[(strlen (host))-1] == ']') {
+ strcpy (tmp,host+1); /* yes, copy number part */
+ tmp[(strlen (tmp))-1] = '\0';
+ if (adr = ip_stringtoaddr (tmp,&adrlen,&family)) {
+ (*bn) (BLOCK_TCPOPEN,NIL);
+ /* get an open socket for this system */
+ sock = tcp_socket_open (family,adr,adrlen,port,tmp,ctrp,hostname = host);
+ (*bn) (BLOCK_NONE,NIL);
+ fs_give ((void **) &adr);
+ }
+ else sprintf (tmp,"Bad format domain-literal: %.80s",host);
+ }
+
+ else { /* lookup host name */
+ if (tcpdebug) {
+ sprintf (tmp,"DNS resolution %.80s",host);
+ mm_log (tmp,TCPDEBUG);
+ }
+ (*bn) (BLOCK_DNSLOOKUP,NIL);/* quell alarms */
+ data = (*bn) (BLOCK_SENSITIVE,NIL);
+ if (!(s = ip_nametoaddr (host,&adrlen,&family,&hostname,&next)))
+ sprintf (tmp,"No such host as %.80s",host);
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ (*bn) (BLOCK_NONE,NIL);
+ if (s) { /* DNS resolution won? */
+ if (tcpdebug) mm_log ("DNS resolution done",TCPDEBUG);
+ do {
+ (*bn) (BLOCK_TCPOPEN,NIL);
+ if (((sock = tcp_socket_open (family,s,adrlen,port,tmp,ctrp,
+ hostname)) < 0) &&
+ (s = ip_nametoaddr (NIL,&adrlen,&family,&hostname,&next)) &&
+ !silent) mm_log (tmp,WARN);
+ (*bn) (BLOCK_NONE,NIL);
+ } while ((sock < 0) && s);/* repeat until success or no more addreses */
+ }
+ }
+ if (sock >= 0) { /* won */
+ stream = (TCPSTREAM *) memset (fs_get (sizeof (TCPSTREAM)),0,
+ sizeof (TCPSTREAM));
+ stream->port = port; /* port number */
+ /* init sockets */
+ stream->tcpsi = stream->tcpso = sock;
+ /* stash in the snuck-in byte */
+ if (stream->ictr = ctr) *(stream->iptr = stream->ibuf) = tmp[0];
+ /* copy official host name */
+ stream->host = cpystr (hostname);
+ if (tcpdebug) mm_log ("Stream open and ready for read",TCPDEBUG);
+ }
+ else if (!silent) mm_log (tmp,ERROR);
+ return stream; /* return success */
+}
+
+/* Open a TCP socket
+ * Accepts: protocol family
+ * address to connect to
+ * address length
+ * port
+ * scratch buffer
+ * pointer to "first byte read in" storage or NIL
+ * host name for error message
+ * Returns: socket if success, else -1 with error string in scratch buffer
+ */
+
+int tcp_socket_open (int family,void *adr,size_t adrlen,unsigned short port,
+ char *tmp,int *ctr,char *hst)
+{
+ int i,ti,sock,flgs;
+ size_t len;
+ time_t now;
+ struct protoent *pt = getprotobyname ("tcp");
+ fd_set fds,efds;
+ struct timeval tmo;
+ struct sockaddr *sadr = ip_sockaddr (family,adr,adrlen,port,&len);
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* fetid Solaris */
+ void *data = (*bn) (BLOCK_SENSITIVE,NIL);
+ sprintf (tmp,"Trying IP address [%s]",ip_sockaddrtostring (sadr));
+ mm_log (tmp,NIL);
+ /* make a socket */
+ if ((sock = socket (sadr->sa_family,SOCK_STREAM,pt ? pt->p_proto : 0)) < 0) {
+ sprintf (tmp,"Unable to create TCP socket: %s",strerror (errno));
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ }
+ else if (sock >= FD_SETSIZE) {/* unselectable sockets are useless */
+ sprintf (tmp,"Unable to create selectable TCP socket (%d >= %d)",
+ sock,FD_SETSIZE);
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ close (sock);
+ sock = -1;
+ errno = EMFILE;
+ }
+
+ else { /* get current socket flags */
+ flgs = fcntl (sock,F_GETFL,0);
+ /* set non-blocking if want open timeout */
+ if (ctr) fcntl (sock,F_SETFL,flgs | FNDELAY);
+ /* open connection */
+ while ((i = connect (sock,sadr,len)) < 0 && (errno == EINTR));
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ if (i < 0) switch (errno) { /* failed? */
+ case EAGAIN: /* DG brain damage */
+ case EINPROGRESS: /* what we expect to happen */
+ case EALREADY: /* or another form of it */
+ case EISCONN: /* restart after interrupt? */
+ case EADDRINUSE: /* restart after interrupt? */
+ break; /* well, not really, it was interrupted */
+ default:
+ sprintf (tmp,"Can't connect to %.80s,%u: %s",hst,(unsigned int) port,
+ strerror (errno));
+ close (sock); /* flush socket */
+ sock = -1;
+ }
+ if ((sock >= 0) && ctr) { /* want open timeout? */
+ now = time (0); /* open timeout */
+ ti = ttmo_open ? now + ttmo_open : 0;
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ FD_SET (sock,&fds); /* block for error or readable */
+ FD_SET (sock,&efds);
+ do { /* block under timeout */
+ tmo.tv_sec = ti ? ti - now : 0;
+ i = select (sock+1,&fds,NIL,&efds,ti ? &tmo : NIL);
+ now = time (0); /* fake timeout if interrupt & time expired */
+ if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0;
+ } while ((i < 0) && (errno == EINTR));
+ if (i > 0) { /* success, make sure really connected */
+ /* restore blocking status */
+ fcntl (sock,F_SETFL,flgs);
+ /* This used to be a zero-byte read(), but that crashes Solaris */
+ /* get socket status */
+ while (((i = *ctr = read (sock,tmp,1)) < 0) && (errno == EINTR));
+ }
+ if (i <= 0) { /* timeout or error? */
+ i = i ? errno : ETIMEDOUT;/* determine error code */
+ close (sock); /* flush socket */
+ sock = -1;
+ errno = i; /* return error code */
+ sprintf (tmp,"Connection failed to %.80s,%lu: %s",hst,
+ (unsigned long) port,strerror (errno));
+ }
+ }
+ }
+ fs_give ((void **) &sadr);
+ return sock; /* return the socket */
+}
+
+/* TCP/IP authenticated open
+ * Accepts: host name
+ * service name
+ * returned user name buffer
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+#define MAXARGV 20
+
+TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ TCPSTREAM *stream = NIL;
+ void *adr;
+ char host[MAILTMPLEN],tmp[MAILTMPLEN],*path,*argv[MAXARGV+1],*r;
+ int i,ti,pipei[2],pipeo[2];
+ size_t len;
+ time_t now;
+ struct timeval tmo;
+ fd_set fds,efds;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+#ifdef SSHPATH /* ssh path defined yet? */
+ if (!sshpath) sshpath = cpystr (SSHPATH);
+#endif
+#ifdef RSHPATH /* rsh path defined yet? */
+ if (!rshpath) rshpath = cpystr (RSHPATH);
+#endif
+ if (*service == '*') { /* want ssh? */
+ /* return immediately if ssh disabled */
+ if (!(sshpath && (ti = sshtimeout))) return NIL;
+ /* ssh command prototype defined yet? */
+ if (!sshcommand) sshcommand = cpystr ("%s %s -l %s exec /etc/r%sd");
+ }
+ /* want rsh? */
+ else if (rshpath && (ti = rshtimeout)) {
+ /* rsh command prototype defined yet? */
+ if (!rshcommand) rshcommand = cpystr ("%s %s -l %s exec /etc/r%sd");
+ }
+ else return NIL; /* rsh disabled */
+ /* look like domain literal? */
+ if (mb->host[0] == '[' && mb->host[i = (strlen (mb->host))-1] == ']') {
+ strcpy (host,mb->host+1); /* yes, copy without brackets */
+ host[i-1] = '\0';
+ /* validate domain literal */
+ if (adr = ip_stringtoaddr (host,&len,&i)) fs_give ((void **) &adr);
+ else {
+ sprintf (tmp,"Bad format domain-literal: %.80s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ }
+ else strcpy (host,tcp_canonical (mb->host));
+
+ if (*service == '*') /* build ssh command */
+ sprintf (tmp,sshcommand,sshpath,host,
+ mb->user[0] ? mb->user : myusername (),service + 1);
+ else sprintf (tmp,rshcommand,rshpath,host,
+ mb->user[0] ? mb->user : myusername (),service);
+ if (tcpdebug) {
+ char msg[MAILTMPLEN];
+ sprintf (msg,"Trying %.100s",tmp);
+ mm_log (msg,TCPDEBUG);
+ }
+ /* parse command into argv */
+ for (i = 1,path = argv[0] = strtok_r (tmp," ",&r);
+ (i < MAXARGV) && (argv[i] = strtok_r (NIL," ",&r)); i++);
+ argv[i] = NIL; /* make sure argv tied off */
+ /* make command pipes */
+ if (pipe (pipei) < 0) return NIL;
+ if ((pipei[0] >= FD_SETSIZE) || (pipei[1] >= FD_SETSIZE) ||
+ (pipe (pipeo) < 0)) {
+ close (pipei[0]); close (pipei[1]);
+ return NIL;
+ }
+ (*bn) (BLOCK_TCPOPEN,NIL); /* quell alarm up here for NeXT */
+ if ((pipeo[0] >= FD_SETSIZE) || (pipeo[1] >= FD_SETSIZE) ||
+ ((i = fork ()) < 0)) { /* make inferior process */
+ close (pipei[0]); close (pipei[1]);
+ close (pipeo[0]); close (pipeo[1]);
+ (*bn) (BLOCK_NONE,NIL);
+ return NIL;
+ }
+ if (!i) { /* if child */
+ alarm (0); /* never have alarms in children */
+ if (!fork ()) { /* make grandchild so it's inherited by init */
+ int cf; /* don't alter parent vars in case vfork() */
+ int maxfd = max (20,max (max(pipei[0],pipei[1]),max(pipeo[0],pipeo[1])));
+ dup2 (pipei[1],1); /* parent's input is my output */
+ dup2 (pipei[1],2); /* parent's input is my error output too */
+ dup2 (pipeo[0],0); /* parent's output is my input */
+ /* close all unnecessary descriptors */
+ for (cf = 3; cf <= maxfd; cf++) close (cf);
+ setpgrp (0,getpid ()); /* be our own process group */
+ _exit (execv (path,argv));/* now run it */
+ }
+ _exit (1); /* child is done */
+ }
+ grim_pid_reap (i,NIL); /* reap child; grandchild now owned by init */
+ close (pipei[1]); /* close child's side of the pipes */
+ close (pipeo[0]);
+
+ /* create TCP/IP stream */
+ stream = (TCPSTREAM *) memset (fs_get (sizeof (TCPSTREAM)),0,
+ sizeof (TCPSTREAM));
+ /* copy remote host name from argument */
+ stream->remotehost = cpystr (stream->host = cpystr (host));
+ stream->tcpsi = pipei[0]; /* init sockets */
+ stream->tcpso = pipeo[1];
+ stream->ictr = 0; /* init input counter */
+ stream->port = 0xffffffff; /* no port number */
+ ti += now = time (0); /* open timeout */
+ tmo.tv_usec = 0; /* initialize usec timeout */
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ FD_SET (stream->tcpsi,&fds); /* set bit in selection vector */
+ FD_SET (stream->tcpsi,&efds); /* set bit in error selection vector */
+ FD_SET (stream->tcpso,&efds); /* set bit in error selection vector */
+ do { /* block under timeout */
+ tmo.tv_sec = ti - now;
+ i = select (max (stream->tcpsi,stream->tcpso)+1,&fds,NIL,&efds,&tmo);
+ now = time (0); /* fake timeout if interrupt & time expired */
+ if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0;
+ } while ((i < 0) && (errno == EINTR));
+ if (i <= 0) { /* timeout or error? */
+ sprintf (tmp,i ? "error in %s to IMAP server" :
+ "%s to IMAP server timed out",(*service == '*') ? "ssh" : "rsh");
+ mm_log (tmp,WARN);
+ tcp_close (stream); /* punt stream */
+ stream = NIL;
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ /* return user name */
+ strcpy (usrbuf,mb->user[0] ? mb->user : myusername ());
+ return stream; /* return success */
+}
+
+/* TCP receive line
+ * Accepts: TCP stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *tcp_getline (TCPSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = tcp_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = tcp_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* TCP receive line or partial line
+ * Accepts: TCP stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!tcp_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!tcp_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* TCP/IP receive buffer
+ * Accepts: TCP/IP stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *s)
+{
+ unsigned long n;
+ /* make sure socket still alive */
+ if (stream->tcpsi < 0) return NIL;
+ /* can transfer bytes from buffer? */
+ if (n = min (size,stream->ictr)) {
+ memcpy (s,stream->iptr,n); /* yes, slurp as much as we can from it */
+ s += n; /* update pointer */
+ stream->iptr +=n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -=n;
+ }
+ if (size) {
+ int i;
+ fd_set fds,efds;
+ struct timeval tmo;
+ time_t t = time (0);
+ blocknotify_t bn=(blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ (*bn) (BLOCK_TCPREAD,NIL);
+ while (size > 0) { /* until request satisfied */
+ time_t tl = time (0);
+ time_t now = tl;
+ time_t ti = ttmo_read ? now + ttmo_read : 0;
+ if (tcpdebug) mm_log ("Reading TCP buffer",TCPDEBUG);
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ /* set bit in selection vectors */
+ FD_SET (stream->tcpsi,&fds);
+ FD_SET (stream->tcpsi,&efds);
+ errno = NIL; /* initially no error */
+ do { /* block under timeout */
+ tmo.tv_sec = ti ? ti - now : 0;
+ i = select (stream->tcpsi+1,&fds,NIL,&efds,ti ? &tmo : NIL);
+ now = time (0); /* fake timeout if interrupt & time expired */
+ if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0;
+ } while ((i < 0) && (errno == EINTR));
+ if (i) { /* non-timeout result from select? */
+ if (i > 0) /* read what we can */
+ while (((i = read (stream->tcpsi,s,(int) min (maxposint,size))) < 0)
+ && (errno == EINTR));
+ if (i <= 0) { /* error seen? */
+ if (tcpdebug) {
+ char tmp[MAILTMPLEN];
+ if (i) sprintf (s = tmp,"TCP buffer read I/O error %d",errno);
+ else s = "TCP buffer read end of file";
+ mm_log (s,TCPDEBUG);
+ }
+ return tcp_abort (stream);
+ }
+ s += i; /* success, point at new place to write */
+ size -= i; /* reduce byte count */
+ if (tcpdebug) mm_log ("Successfully read TCP buffer",TCPDEBUG);
+ }
+ /* timeout, punt unless told not to */
+ else if (!tmoh || !(*tmoh) (now - t,now - tl, stream->host)) {
+ if (tcpdebug) mm_log ("TCP buffer read timeout",TCPDEBUG);
+ return tcp_abort (stream);
+ }
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ }
+ *s = '\0'; /* tie off string */
+ return LONGT;
+}
+
+/* TCP/IP receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getdata (TCPSTREAM *stream)
+{
+ int i;
+ fd_set fds,efds;
+ struct timeval tmo;
+ time_t t = time (0);
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (stream->tcpsi < 0) return NIL;
+ (*bn) (BLOCK_TCPREAD,NIL);
+ while (stream->ictr < 1) { /* if nothing in the buffer */
+ time_t tl = time (0); /* start of request */
+ time_t now = tl;
+ time_t ti = ttmo_read ? now + ttmo_read : 0;
+ if (tcpdebug) mm_log ("Reading TCP data",TCPDEBUG);
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ FD_SET (stream->tcpsi,&fds);/* set bit in selection vectors */
+ FD_SET (stream->tcpsi,&efds);
+ errno = NIL; /* initially no error */
+ do { /* block under timeout */
+ tmo.tv_sec = ti ? ti - now : 0;
+ i = select (stream->tcpsi+1,&fds,NIL,&efds,ti ? &tmo : NIL);
+ now = time (0); /* fake timeout if interrupt & time expired */
+ if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0;
+ } while ((i < 0) && (errno == EINTR));
+ if (i) { /* non-timeout result from select? */
+ /* read what we can */
+ if (i > 0) while (((i = read (stream->tcpsi,stream->ibuf,BUFLEN)) < 0) &&
+ (errno == EINTR));
+ if (i <= 0) { /* error seen? */
+ if (tcpdebug) {
+ char *s,tmp[MAILTMPLEN];
+ if (i) sprintf (s = tmp,"TCP data read I/O error %d",errno);
+ else s = "TCP data read end of file";
+ mm_log (s,TCPDEBUG);
+ }
+ return tcp_abort (stream);
+ }
+ stream->ictr = i; /* success, set new count and pointer */
+ stream->iptr = stream->ibuf;
+ if (tcpdebug) mm_log ("Successfully read TCP data",TCPDEBUG);
+ }
+ /* timeout, punt unless told not to */
+ else if (!tmoh || !(*tmoh) (now - t,now - tl, stream->host)) {
+ if (tcpdebug) mm_log ("TCP data read timeout",TCPDEBUG);
+ return tcp_abort (stream);/* error or timeout no-continue */
+ }
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return T;
+}
+
+/* TCP/IP send string as record
+ * Accepts: TCP/IP stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long tcp_soutr (TCPSTREAM *stream,char *string)
+{
+ return tcp_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* TCP/IP send string
+ * Accepts: TCP/IP stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
+{
+ int i;
+ fd_set fds,efds;
+ struct timeval tmo;
+ time_t t = time (0);
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (stream->tcpso < 0) return NIL;
+ (*bn) (BLOCK_TCPWRITE,NIL);
+ while (size > 0) { /* until request satisfied */
+ time_t tl = time (0); /* start of request */
+ time_t now = tl;
+ time_t ti = ttmo_write ? now + ttmo_write : 0;
+ if (tcpdebug) mm_log ("Writing to TCP",TCPDEBUG);
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ FD_SET (stream->tcpso,&fds);/* set bit in selection vector */
+ FD_SET(stream->tcpso,&efds);/* set bit in error selection vector */
+ errno = NIL; /* block and write */
+ do { /* block under timeout */
+ tmo.tv_sec = ti ? ti - now : 0;
+ i = select (stream->tcpso+1,NIL,&fds,&efds,ti ? &tmo : NIL);
+ now = time (0); /* fake timeout if interrupt & time expired */
+ if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0;
+ } while ((i < 0) && (errno == EINTR));
+ if (i) { /* non-timeout result from select? */
+ /* write what we can */
+ if (i > 0) while (((i = write (stream->tcpso,string,size)) < 0) &&
+ (errno == EINTR));
+ if (i <= 0) { /* error seen? */
+ if (tcpdebug) {
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"TCP write I/O error %d",errno);
+ mm_log (tmp,TCPDEBUG);
+ }
+ return tcp_abort (stream);
+ }
+ string += i; /* how much we sent */
+ size -= i; /* count this size */
+ if (tcpdebug) mm_log ("successfully wrote to TCP",TCPDEBUG);
+ }
+ /* timeout, punt unless told not to */
+ else if (!tmoh || !(*tmoh) (now - t,now - tl, stream->host)) {
+ if (tcpdebug) mm_log ("TCP write timeout",TCPDEBUG);
+ return tcp_abort (stream);
+ }
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return T; /* all done */
+}
+
+/* TCP/IP close
+ * Accepts: TCP/IP stream
+ */
+
+void tcp_close (TCPSTREAM *stream)
+{
+ tcp_abort (stream); /* nuke the stream */
+ /* flush host names */
+ if (stream->host) fs_give ((void **) &stream->host);
+ if (stream->remotehost) fs_give ((void **) &stream->remotehost);
+ if (stream->localhost) fs_give ((void **) &stream->localhost);
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* TCP/IP abort stream
+ * Accepts: TCP/IP stream
+ * Returns: NIL always
+ */
+
+long tcp_abort (TCPSTREAM *stream)
+{
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (stream->tcpsi >= 0) { /* no-op if no socket */
+ (*bn) (BLOCK_TCPCLOSE,NIL);
+ close (stream->tcpsi); /* nuke the socket */
+ if (stream->tcpsi != stream->tcpso) close (stream->tcpso);
+ stream->tcpsi = stream->tcpso = -1;
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return NIL;
+}
+
+/* TCP/IP get host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_host (TCPSTREAM *stream)
+{
+ return stream->host; /* use tcp_remotehost() if want guarantees */
+}
+
+
+/* TCP/IP get remote host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_remotehost (TCPSTREAM *stream)
+{
+ if (!stream->remotehost) {
+ size_t sadrlen;
+ struct sockaddr *sadr = ip_newsockaddr (&sadrlen);
+ stream->remotehost = /* get socket's peer name */
+ getpeername (stream->tcpsi,sadr,(void *) &sadrlen) ?
+ cpystr (stream->host) : tcp_name (sadr,NIL);
+ fs_give ((void **) &sadr);
+ }
+ return stream->remotehost;
+}
+
+
+/* TCP/IP return port for this stream
+ * Accepts: TCP/IP stream
+ * Returns: port number for this stream
+ */
+
+unsigned long tcp_port (TCPSTREAM *stream)
+{
+ return stream->port; /* return port number */
+}
+
+
+/* TCP/IP get local host name
+ * Accepts: TCP/IP stream
+ * Returns: local host name
+ */
+
+char *tcp_localhost (TCPSTREAM *stream)
+{
+ if (!stream->localhost) {
+ size_t sadrlen;
+ struct sockaddr *sadr = ip_newsockaddr (&sadrlen);
+ stream->localhost = /* get socket's name */
+ ((stream->port & 0xffff000) ||
+ getsockname (stream->tcpsi,sadr,(void *) &sadrlen)) ?
+ cpystr (mylocalhost ()) : tcp_name (sadr,NIL);
+ fs_give ((void **) &sadr);
+ }
+ return stream->localhost; /* return local host name */
+}
+
+/* TCP/IP get client host address (server calls only)
+ * Returns: client host address
+ */
+
+char *tcp_clientaddr ()
+{
+ if (!myClientAddr) {
+ size_t sadrlen;
+ struct sockaddr *sadr = ip_newsockaddr (&sadrlen);
+ if (getpeername (0,sadr,(void *) &sadrlen))
+ myClientAddr = cpystr ("UNKNOWN");
+ else { /* get stdin's peer name */
+ myClientAddr = cpystr (ip_sockaddrtostring (sadr));
+ if (myClientPort < 0) myClientPort = ip_sockaddrtoport (sadr);
+ }
+ fs_give ((void **) &sadr);
+ }
+ return myClientAddr;
+}
+
+
+/* TCP/IP get client host name (server calls only)
+ * Returns: client host name
+ */
+
+char *tcp_clienthost ()
+{
+ if (!myClientHost) {
+ size_t sadrlen;
+ struct sockaddr *sadr = ip_newsockaddr (&sadrlen);
+ if (getpeername (0,sadr,(void *) &sadrlen)) {
+ char *s,*t,*v,tmp[MAILTMPLEN];
+ if ((s = getenv (t = "SSH_CLIENT")) ||
+ (s = getenv (t = "KRB5REMOTEADDR")) ||
+ (s = getenv (t = "SSH2_CLIENT"))) {
+ if (v = strchr (s,' ')) *v = '\0';
+ sprintf (v = tmp,"%.80s=%.80s",t,s);
+ }
+ else v = "UNKNOWN";
+ myClientHost = cpystr (v);
+ }
+ else { /* get stdin's peer name */
+ myClientHost = tcp_name (sadr,T);
+ if (!myClientAddr) myClientAddr = cpystr (ip_sockaddrtostring (sadr));
+ if (myClientPort < 0) myClientPort = ip_sockaddrtoport (sadr);
+ }
+ fs_give ((void **) &sadr);
+ }
+ return myClientHost;
+}
+
+
+/* TCP/IP get client port number (server calls only)
+ * Returns: client port number
+ */
+
+long tcp_clientport ()
+{
+ if (!myClientHost && !myClientAddr) tcp_clientaddr ();
+ return myClientPort;
+}
+
+/* TCP/IP get server host address (server calls only)
+ * Returns: server host address
+ */
+
+char *tcp_serveraddr ()
+{
+ if (!myServerAddr) {
+ size_t sadrlen;
+ struct sockaddr *sadr = ip_newsockaddr (&sadrlen);
+ if (getsockname (0,sadr,(void *) &sadrlen))
+ myServerAddr = cpystr ("UNKNOWN");
+ else { /* get stdin's name */
+ myServerAddr = cpystr (ip_sockaddrtostring (sadr));
+ if (myServerPort < 0) myServerPort = ip_sockaddrtoport (sadr);
+ }
+ fs_give ((void **) &sadr);
+ }
+ return myServerAddr;
+}
+
+
+/* TCP/IP get server host name (server calls only)
+ * Returns: server host name
+ */
+
+char *tcp_serverhost ()
+{
+ if (!myServerHost) { /* once-only */
+ size_t sadrlen;
+ struct sockaddr *sadr = ip_newsockaddr (&sadrlen);
+ /* get stdin's name */
+ if (getsockname (0,sadr,(void *) &sadrlen))
+ myServerHost = cpystr (mylocalhost ());
+ else { /* get stdin's name */
+ myServerHost = tcp_name (sadr,NIL);
+ if (!myServerAddr) myServerAddr = cpystr (ip_sockaddrtostring (sadr));
+ if (myServerPort < 0) myServerPort = ip_sockaddrtoport (sadr);
+ }
+ fs_give ((void **) &sadr);
+ }
+ return myServerHost;
+}
+
+
+/* TCP/IP get server port number (server calls only)
+ * Returns: server port number
+ */
+
+long tcp_serverport ()
+{
+ if (!myServerHost && !myServerAddr) tcp_serveraddr ();
+ return myServerPort;
+}
+
+/* TCP/IP return canonical form of host name
+ * Accepts: host name
+ * Returns: canonical form of host name
+ */
+
+char *tcp_canonical (char *name)
+{
+ char *ret,host[MAILTMPLEN];
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ void *data;
+ /* look like domain literal? */
+ if (name[0] == '[' && name[strlen (name) - 1] == ']') return name;
+ (*bn) (BLOCK_DNSLOOKUP,NIL); /* quell alarms */
+ data = (*bn) (BLOCK_SENSITIVE,NIL);
+ if (tcpdebug) {
+ sprintf (host,"DNS canonicalization %.80s",name);
+ mm_log (host,TCPDEBUG);
+ }
+ /* get canonical name */
+ if (!ip_nametoaddr (name,NIL,NIL,&ret,NIL)) ret = name;
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ (*bn) (BLOCK_NONE,NIL); /* alarms OK now */
+ if (tcpdebug) mm_log ("DNS canonicalization done",TCPDEBUG);
+ return ret;
+}
+
+/* TCP/IP return name from socket
+ * Accepts: socket
+ * verbose flag
+ * Returns: cpystr name
+ */
+
+char *tcp_name (struct sockaddr *sadr,long flag)
+{
+ char *ret,*t,adr[MAILTMPLEN],tmp[MAILTMPLEN];
+ sprintf (ret = adr,"[%.80s]",ip_sockaddrtostring (sadr));
+ if (allowreversedns) {
+ blocknotify_t bn = (blocknotify_t)mail_parameters(NIL,GET_BLOCKNOTIFY,NIL);
+ void *data;
+ if (tcpdebug) {
+ sprintf (tmp,"Reverse DNS resolution %s",adr);
+ mm_log (tmp,TCPDEBUG);
+ }
+ (*bn) (BLOCK_DNSLOOKUP,NIL);/* quell alarms */
+ data = (*bn) (BLOCK_SENSITIVE,NIL);
+ /* translate address to name */
+ if (t = tcp_name_valid (ip_sockaddrtoname (sadr))) {
+ /* produce verbose form if needed */
+ if (flag) sprintf (ret = tmp,"%s %s",t,adr);
+ else ret = t;
+ }
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ (*bn) (BLOCK_NONE,NIL); /* alarms OK now */
+ if (tcpdebug) mm_log ("Reverse DNS resolution done",TCPDEBUG);
+ }
+ return cpystr (ret);
+}
+
+
+/* TCP/IP validate name
+ * Accepts: domain name
+ * Returns: name if valid, NIL otherwise
+ */
+
+char *tcp_name_valid (char *s)
+{
+ int c;
+ char *ret,*tail;
+ /* must be non-empty and not too long */
+ if ((ret = (s && *s) ? s : NIL) && (tail = ret + NETMAXHOST)) {
+ /* must be alnum, dot, or hyphen */
+ while ((c = *s++) && (s <= tail) &&
+ (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) ||
+ ((c >= '0') && (c <= '9')) || (c == '-') || (c == '.')));
+ if (c) ret = NIL;
+ }
+ return ret;
+}
+
+/* TCP/IP check if client is given host name
+ * Accepts: candidate host name
+ * Returns: T if match, NIL otherwise
+ */
+
+long tcp_isclienthost (char *host)
+{
+ int family;
+ size_t adrlen,sadrlen,len;
+ void *adr,*next;
+ struct sockaddr *sadr;
+ long ret = NIL;
+ /* make sure that myClientAddr is set */
+ if (tcp_clienthost () && myClientAddr)
+ /* get sockaddr of client */
+ for (adr = ip_nametoaddr (host,&adrlen,&family,NIL,&next); adr && !ret;
+ adr = ip_nametoaddr (NIL,&adrlen,&family,NIL,&next)) {
+ /* build sockaddr of given address */
+ sadr = ip_sockaddr (family,adr,adrlen,1,&len);
+ if (!strcmp (myClientAddr,ip_sockaddrtostring (sadr))) ret = LONGT;
+ fs_give ((void **) &sadr); /* done with client sockaddr */
+ }
+ return ret;
+}
+
+/* Following statement must be at end of this module */
+
+#undef fork /* undo any use of vfork() */
diff --git a/imap/src/osdep/unix/tcp_unix.h b/imap/src/osdep/unix/tcp_unix.h
new file mode 100644
index 00000000..d4b29797
--- /dev/null
+++ b/imap/src/osdep/unix/tcp_unix.h
@@ -0,0 +1,48 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* TCP input buffer */
+
+#define BUFLEN 8192
+
+
+/* TCP I/O stream */
+
+#define TCPSTREAM struct tcp_stream
+TCPSTREAM {
+ char *host; /* host name */
+ unsigned long port; /* port number */
+ char *localhost; /* local host name */
+ char *remotehost; /* remote host name */
+ int tcpsi; /* input socket */
+ int tcpso; /* output socket */
+ int ictr; /* input counter */
+ char *iptr; /* input pointer */
+ char ibuf[BUFLEN]; /* input buffer */
+};
diff --git a/imap/src/osdep/unix/tenex.c b/imap/src/osdep/unix/tenex.c
new file mode 100644
index 00000000..eee61fba
--- /dev/null
+++ b/imap/src/osdep/unix/tenex.c
@@ -0,0 +1,1470 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Tenex mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 22 May 1990
+ * Last Edited: 11 October 2007
+ */
+
+
+/* FILE TIME SEMANTICS
+ *
+ * The atime is the last read time of the file.
+ * The mtime is the last flags update time of the file.
+ * The ctime is the last write time of the file.
+ *
+ * TEXT SIZE SEMANTICS
+ *
+ * Most of the text sizes are in internal (LF-only) form, except for the
+ * msg.text size. Beware.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <sys/stat.h>
+#include "misc.h"
+#include "dummy.h"
+
+/* TENEX I/O stream local data */
+
+typedef struct tenex_local {
+ unsigned int shouldcheck: 1; /* if ping should do a check instead */
+ unsigned int mustcheck: 1; /* if ping must do a check instead */
+ int fd; /* file descriptor for I/O */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* local snarf time */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned long uid; /* current text uid */
+ SIZEDTEXT text; /* current text */
+} TENEXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((TENEXLOCAL *) stream->local)
+
+
+/* Function prototypes */
+
+DRIVER *tenex_valid (char *name);
+int tenex_isvalid (char *name,char *tmp);
+void *tenex_parameters (long function,void *value);
+void tenex_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void tenex_list (MAILSTREAM *stream,char *ref,char *pat);
+void tenex_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long tenex_create (MAILSTREAM *stream,char *mailbox);
+long tenex_delete (MAILSTREAM *stream,char *mailbox);
+long tenex_rename (MAILSTREAM *stream,char *old,char *newname);
+long tenex_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *tenex_open (MAILSTREAM *stream);
+void tenex_close (MAILSTREAM *stream,long options);
+void tenex_fast (MAILSTREAM *stream,char *sequence,long flags);
+void tenex_flags (MAILSTREAM *stream,char *sequence,long flags);
+char *tenex_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long tenex_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+void tenex_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
+void tenex_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long tenex_ping (MAILSTREAM *stream);
+void tenex_check (MAILSTREAM *stream);
+void tenex_snarf (MAILSTREAM *stream);
+long tenex_expunge (MAILSTREAM *stream,char *sequence,long options);
+long tenex_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long tenex_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+unsigned long tenex_size (MAILSTREAM *stream,unsigned long m);
+char *tenex_file (char *dst,char *name);
+long tenex_parse (MAILSTREAM *stream);
+MESSAGECACHE *tenex_elt (MAILSTREAM *stream,unsigned long msgno);
+void tenex_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
+void tenex_update_status (MAILSTREAM *stream,unsigned long msgno,
+ long syncflag);
+unsigned long tenex_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size);
+
+/* Tenex mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER tenexdriver = {
+ "tenex", /* driver name */
+ DR_LOCAL|DR_MAIL|DR_NOSTICKY|DR_LOCKING,
+ /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ tenex_valid, /* mailbox is valid for us */
+ tenex_parameters, /* manipulate parameters */
+ tenex_scan, /* scan mailboxes */
+ tenex_list, /* list mailboxes */
+ tenex_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ tenex_delete, /* delete mailbox */
+ tenex_rename, /* rename mailbox */
+ tenex_status, /* status of mailbox */
+ tenex_open, /* open mailbox */
+ tenex_close, /* close mailbox */
+ tenex_fast, /* fetch message "fast" attributes */
+ tenex_flags, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ tenex_header, /* fetch message header */
+ tenex_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ tenex_flag, /* modify flags */
+ tenex_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ tenex_ping, /* ping mailbox to see if still alive */
+ tenex_check, /* check for new messages */
+ tenex_expunge, /* expunge deleted messages */
+ tenex_copy, /* copy messages to another mailbox */
+ tenex_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM tenexproto = {&tenexdriver};
+
+/* Tenex mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *tenex_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ return tenex_isvalid (name,tmp) ? &tenexdriver : NIL;
+}
+
+
+/* Tenex mail test for valid mailbox
+ * Accepts: mailbox name
+ * Returns: T if valid, NIL otherwise
+ */
+
+int tenex_isvalid (char *name,char *tmp)
+{
+ int fd;
+ int ret = NIL;
+ char *s,file[MAILTMPLEN];
+ struct stat sbuf;
+ time_t tp[2];
+ errno = EINVAL; /* assume invalid argument */
+ /* if file, get its status */
+ if ((s = tenex_file (file,name)) && !stat (s,&sbuf)) {
+ if (!sbuf.st_size) { /* allow empty file if INBOX */
+ if ((s = mailboxfile (tmp,name)) && !*s) ret = T;
+ else errno = 0; /* empty file */
+ }
+ else if ((fd = open (file,O_RDONLY,NIL)) >= 0) {
+ memset (tmp,'\0',MAILTMPLEN);
+ if ((read (fd,tmp,64) >= 0) && (s = strchr (tmp,'\012')) &&
+ (s[-1] != '\015')) { /* valid format? */
+ *s = '\0'; /* tie off header */
+ /* must begin with dd-mmm-yy" */
+ ret = (((tmp[2] == '-' && tmp[6] == '-') ||
+ (tmp[1] == '-' && tmp[5] == '-')) &&
+ (s = strchr (tmp+18,',')) && strchr (s+2,';')) ? T : NIL;
+ }
+ else errno = -1; /* bogus format */
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if (sbuf.st_ctime > sbuf.st_atime) {
+ tp[0] = sbuf.st_atime; /* preserve atime and mtime */
+ tp[1] = sbuf.st_mtime;
+ utime (file,tp); /* set the times */
+ }
+ }
+ }
+ /* in case INBOX but not tenex format */
+ else if ((errno == ENOENT) && !compare_cstring (name,"INBOX")) errno = -1;
+ return ret; /* return what we should */
+}
+
+/* Tenex manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tenex_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = tenex_file ((char *) value,"INBOX");
+ break;
+ }
+ return ret;
+}
+
+
+/* Tenex mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void tenex_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* Tenex mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void tenex_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* Tenex mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void tenex_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* Tenex mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long tenex_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return tenex_rename (stream,mailbox,NIL);
+}
+
+
+/* Tenex mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long tenex_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = T;
+ char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ int fd,ld;
+ struct stat sbuf;
+ if (!tenex_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'/')) && !s[1])))) {
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else if ((fd = open (file,O_RDWR,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get exclusive parse/append permission */
+ if ((ld = lockfd (fd,lock,LOCK_EX)) < 0) {
+ MM_LOG ("Unable to lock rename mailbox",ERROR);
+ return NIL;
+ }
+ /* lock out other users */
+ if (flock (fd,LOCK_EX|LOCK_NB)) {
+ close (fd); /* couldn't lock, give up on it then */
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+ MM_LOG (tmp,ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ return NIL;
+ }
+
+ if (newname) { /* want rename? */
+ if (s = strrchr (tmp,'/')) {/* found superior to destination name? */
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp,get_dir_protection (newname)))
+ ret = NIL;
+ else *s = c; /* restore full name */
+ }
+ /* rename the file */
+ if (ret && rename (file,tmp)) {
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ }
+ else if (unlink (file)) {
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* set failure */
+ }
+ flock (fd,LOCK_UN); /* release lock on the file */
+ close (fd); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ /* recreate file if renamed INBOX */
+ if (ret && !compare_cstring (old,"INBOX")) dummy_create (NIL,"mail.txt");
+ return ret; /* return success */
+}
+
+/* Tenex Mail status
+ * Accepts: mail stream
+ * mailbox name
+ * status flags
+ * Returns: T on success, NIL on failure
+ */
+
+long tenex_status (MAILSTREAM *stream,char *mbx,long flags)
+{
+ MAILSTATUS status;
+ unsigned long i;
+ MAILSTREAM *tstream = NIL;
+ MAILSTREAM *systream = NIL;
+ /* make temporary stream (unless this mbx) */
+ if (!stream && !(stream = tstream =
+ mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
+ status.flags = flags; /* return status values */
+ status.messages = stream->nmsgs;
+ status.recent = stream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
+ if (!mail_elt (stream,i)->seen) status.unseen++;
+ status.uidnext = stream->uid_last + 1;
+ status.uidvalidity = stream->uid_validity;
+ /* calculate post-snarf results */
+ if (!status.recent && stream->inbox &&
+ (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) {
+ status.messages += systream->nmsgs;
+ status.recent += systream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1; i <= systream->nmsgs; i++)
+ if (!mail_elt (systream,i)->seen) status.unseen++;
+ /* kludge but probably good enough */
+ status.uidnext += systream->nmsgs;
+ }
+ MM_STATUS(stream,mbx,&status);/* pass status to main program */
+ if (tstream) mail_close (tstream);
+ if (systream) mail_close (systream);
+ return T; /* success */
+}
+
+/* Tenex mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *tenex_open (MAILSTREAM *stream)
+{
+ int fd,ld;
+ char tmp[MAILTMPLEN];
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&tenexproto);
+ if (stream->local) fatal ("tenex recycle stream");
+ user_flags (stream); /* set up user flags */
+ /* canonicalize the mailbox name */
+ if (!tenex_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ MM_LOG (tmp,ERROR);
+ }
+ if (stream->rdonly ||
+ (fd = open (tmp,O_RDWR,NIL)) < 0) {
+ if ((fd = open (tmp,O_RDONLY,NIL)) < 0) {
+ sprintf (tmp,"Can't open mailbox: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ else if (!stream->rdonly) { /* got it, but readonly */
+ MM_LOG ("Can't get write access to mailbox, access is readonly",WARN);
+ stream->rdonly = T;
+ }
+ }
+ stream->local = fs_get (sizeof (TENEXLOCAL));
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE);
+ LOCAL->text.size = CHUNKSIZE - 1;
+
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ LOCAL->fd = fd; /* bind the file */
+ /* flush old name */
+ fs_give ((void **) &stream->mailbox);
+ /* save canonical name */
+ stream->mailbox = cpystr (tmp);
+ /* get shared parse permission */
+ if ((ld = lockfd (fd,tmp,LOCK_SH)) < 0) {
+ MM_LOG ("Unable to lock open mailbox",ERROR);
+ return NIL;
+ }
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH); /* lock the file */
+ (*bn) (BLOCK_NONE,NIL);
+ unlockfd (ld,tmp); /* release shared parse permission */
+ LOCAL->filesize = 0; /* initialize parsed file size */
+ /* time not set up yet */
+ LOCAL->lastsnarf = LOCAL->filetime = 0;
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ stream->sequence++; /* bump sequence number */
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ if (tenex_ping (stream) && !stream->nmsgs)
+ MM_LOG ("Mailbox is empty",(long) NIL);
+ if (!LOCAL) return NIL; /* failure if stream died */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft =
+ stream->rdonly ? NIL : T;
+ stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
+ return stream; /* return stream to caller */
+}
+
+/* Tenex mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void tenex_close (MAILSTREAM *stream,long options)
+{
+ if (stream && LOCAL) { /* only if a file is open */
+ int silent = stream->silent;
+ stream->silent = T; /* note this stream is dying */
+ if (options & CL_EXPUNGE) tenex_expunge (stream,NIL,NIL);
+ stream->silent = silent; /* restore previous status */
+ flock (LOCAL->fd,LOCK_UN); /* unlock local file */
+ close (LOCAL->fd); /* close the local file */
+ /* free local text buffer */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* Tenex mail fetch fast data
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ */
+
+void tenex_fast (MAILSTREAM *stream,char *sequence,long flags)
+{
+ STRING bs;
+ MESSAGECACHE *elt;
+ unsigned long i;
+ if (stream && LOCAL &&
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ if (!elt->rfc822_size) { /* have header size yet? */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.special.text.size,L_SET);
+ /* resize bigbuf if necessary */
+ if (LOCAL->buflen < elt->private.msg.full.text.size) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buflen = elt->private.msg.full.text.size;
+ LOCAL->buf = (char *) fs_get (LOCAL->buflen + 1);
+ }
+ /* tie off string */
+ LOCAL->buf[elt->private.msg.full.text.size] = '\0';
+ /* read in the message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.full.text.size);
+ INIT (&bs,mail_string,(void *) LOCAL->buf,
+ elt->private.msg.full.text.size);
+ /* calculate its CRLF size */
+ elt->rfc822_size = strcrlflen (&bs);
+ }
+ tenex_elt (stream,i); /* get current flags from file */
+ }
+}
+
+
+/* Tenex mail fetch flags
+ * Accepts: MAIL stream
+ * sequence
+ * option flags
+ * Sniffs at file to get flags
+ */
+
+void tenex_flags (MAILSTREAM *stream,char *sequence,long flags)
+{
+ unsigned long i;
+ if (stream && LOCAL &&
+ ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)))
+ for (i = 1; i <= stream->nmsgs; i++)
+ if (mail_elt (stream,i)->sequence) tenex_elt (stream,i);
+}
+
+/* TENEX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+char *tenex_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ char *s;
+ unsigned long i;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ /* get to header position */
+ lseek (LOCAL->fd,tenex_hdrpos (stream,msgno,&i),L_SET);
+ if (flags & FT_INTERNAL) {
+ if (i > LOCAL->buflen) { /* resize if not enough space */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get (LOCAL->buflen = i + 1);
+ }
+ /* slurp the data */
+ read (LOCAL->fd,LOCAL->buf,*length = i);
+ }
+ else {
+ s = (char *) fs_get (i + 1);/* get readin buffer */
+ s[i] = '\0'; /* tie off string */
+ read (LOCAL->fd,s,i); /* slurp the data */
+ /* make CRLF copy of string */
+ *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s,i);
+ fs_give ((void **) &s); /* free readin buffer */
+ }
+ return (char *) LOCAL->buf;
+}
+
+/* TENEX mail fetch message text (body only)
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T, always
+ */
+
+long tenex_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ char *s;
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ /* get message status */
+ elt = tenex_elt (stream,msgno);
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ elt->seen = T; /* mark message as seen */
+ /* recalculate status */
+ tenex_update_status (stream,msgno,T);
+ MM_FLAGS (stream,msgno);
+ }
+ if (flags & FT_INTERNAL) { /* if internal representation wanted */
+ /* find header position */
+ i = tenex_hdrpos (stream,msgno,&j);
+ if (i > LOCAL->buflen) { /* resize if not enough space */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get (LOCAL->buflen = i + 1);
+ }
+ /* go to text position */
+ lseek (LOCAL->fd,i + j,L_SET);
+ /* slurp the data */
+ read (LOCAL->fd,LOCAL->buf,i);
+ /* set up stringstruct for internal */
+ INIT (bs,mail_string,LOCAL->buf,i);
+ }
+ else { /* normal form, previous text cached? */
+ if (elt->private.uid == LOCAL->uid)
+ i = elt->private.msg.text.text.size;
+ else { /* not cached, cache it now */
+ LOCAL->uid = elt->private.uid;
+ /* find header position */
+ i = tenex_hdrpos (stream,msgno,&j);
+ /* go to text position */
+ lseek (LOCAL->fd,i + j,L_SET);
+ s = (char *) fs_get ((i = tenex_size (stream,msgno) - j) + 1);
+ s[i] = '\0'; /* tie off string */
+ read (LOCAL->fd,s,i); /* slurp the data */
+ /* make CRLF copy of string */
+ i = elt->private.msg.text.text.size =
+ strcrlfcpy (&LOCAL->text.data,&LOCAL->text.size,s,i);
+ fs_give ((void **) &s); /* free readin buffer */
+ }
+ /* set up stringstruct */
+ INIT (bs,mail_string,LOCAL->text.data,i);
+ }
+ return T; /* success */
+}
+
+/* Tenex mail modify flags
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ * option flags
+ */
+
+void tenex_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
+{
+ time_t tp[2];
+ struct stat sbuf;
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* make sure read comes after all that */
+ utime (stream->mailbox,tp);
+ }
+}
+
+
+/* Tenex mail per-message modify flags
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void tenex_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ struct stat sbuf;
+ /* maybe need to do a checkpoint? */
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ LOCAL->filetime = 0; /* don't do this test for any other messages */
+ }
+ /* recalculate status */
+ tenex_update_status (stream,elt->msgno,NIL);
+}
+
+/* Tenex mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream still alive, NIL if not
+ */
+
+long tenex_ping (MAILSTREAM *stream)
+{
+ unsigned long i = 1;
+ long r = T;
+ int ld;
+ char lock[MAILTMPLEN];
+ struct stat sbuf;
+ if (stream && LOCAL) { /* only if stream already open */
+ fstat (LOCAL->fd,&sbuf); /* get current file poop */
+ if (LOCAL->filetime && !(LOCAL->mustcheck || LOCAL->shouldcheck) &&
+ (LOCAL->filetime < sbuf.st_mtime)) LOCAL->shouldcheck = T;
+ /* check for changed message status */
+ if (LOCAL->mustcheck || LOCAL->shouldcheck) {
+ LOCAL->filetime = sbuf.st_mtime;
+ if (LOCAL->shouldcheck) /* babble when we do this unilaterally */
+ MM_NOTIFY (stream,"[CHECK] Checking for flag updates",NIL);
+ while (i <= stream->nmsgs) tenex_elt (stream,i++);
+ LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
+ }
+ /* get shared parse/append permission */
+ if ((sbuf.st_size != LOCAL->filesize) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) {
+ /* parse resulting mailbox */
+ r = (tenex_parse (stream)) ? T : NIL;
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ if (LOCAL) { /* stream must still be alive */
+ /* snarf if this is a read-write inbox */
+ if (stream->inbox && !stream->rdonly) {
+ tenex_snarf (stream);
+ fstat (LOCAL->fd,&sbuf);/* see if file changed now */
+ if ((sbuf.st_size != LOCAL->filesize) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_SH)) >= 0)) {
+ /* parse resulting mailbox */
+ r = (tenex_parse (stream)) ? T : NIL;
+ unlockfd (ld,lock); /* release shared parse/append permission */
+ }
+ }
+ }
+ }
+ return r; /* return result of the parse */
+}
+
+
+/* Tenex mail check mailbox (reparses status too)
+ * Accepts: MAIL stream
+ */
+
+void tenex_check (MAILSTREAM *stream)
+{
+ /* mark that a check is desired */
+ if (LOCAL) LOCAL->mustcheck = T;
+ if (tenex_ping (stream)) MM_LOG ("Check completed",(long) NIL);
+}
+
+/* Tenex mail snarf messages from system inbox
+ * Accepts: MAIL stream
+ */
+
+void tenex_snarf (MAILSTREAM *stream)
+{
+ unsigned long i = 0;
+ unsigned long j,r,hdrlen,txtlen;
+ struct stat sbuf;
+ char *hdr,*txt,lock[MAILTMPLEN],tmp[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ MAILSTREAM *sysibx = NIL;
+ int ld;
+ /* give up if can't get exclusive permission */
+ if ((time (0) >= (LOCAL->lastsnarf +
+ (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) &&
+ strcmp (sysinbox (),stream->mailbox) &&
+ ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) >= 0)) {
+ MM_CRITICAL (stream); /* go critical */
+ /* sizes match and anything in sysinbox? */
+ if (!stat (sysinbox (),&sbuf) && sbuf.st_size &&
+ !fstat (LOCAL->fd,&sbuf) && (sbuf.st_size == LOCAL->filesize) &&
+ (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
+ (!sysibx->rdonly) && (r = sysibx->nmsgs)) {
+ /* yes, go to end of file in our mailbox */
+ lseek (LOCAL->fd,sbuf.st_size,L_SET);
+ /* for each message in sysibx mailbox */
+ while (r && (++i <= sysibx->nmsgs)) {
+ /* snarf message from system INBOX */
+ hdr = cpystr (mail_fetchheader_full(sysibx,i,NIL,&hdrlen,FT_INTERNAL));
+ txt = mail_fetchtext_full (sysibx,i,&txtlen,FT_INTERNAL|FT_PEEK);
+ /* if have a message */
+ if (j = hdrlen + txtlen) {
+ /* calculate header line */
+ mail_date (LOCAL->buf,elt = mail_elt (sysibx,i));
+ sprintf (LOCAL->buf + strlen (LOCAL->buf),
+ ",%lu;0000000000%02o\n",j,(unsigned)
+ ((fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ /* copy message */
+ if ((write (LOCAL->fd,LOCAL->buf,strlen (LOCAL->buf)) < 0) ||
+ (write (LOCAL->fd,hdr,hdrlen) < 0) ||
+ (write (LOCAL->fd,txt,txtlen) < 0)) r = 0;
+ }
+ fs_give ((void **) &hdr);
+ }
+
+ /* make sure all the updates take */
+ if (fsync (LOCAL->fd)) r = 0;
+ if (r) { /* delete all the messages we copied */
+ if (r == 1) strcpy (tmp,"1");
+ else sprintf (tmp,"1:%lu",r);
+ mail_flag (sysibx,tmp,"\\Deleted",ST_SET);
+ mail_expunge (sysibx); /* now expunge all those messages */
+ }
+ else {
+ sprintf (LOCAL->buf,"Can't copy new mail: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,WARN);
+ ftruncate (LOCAL->fd,sbuf.st_size);
+ }
+ fstat (LOCAL->fd,&sbuf); /* yes, get current file size */
+ LOCAL->filetime = sbuf.st_mtime;
+ }
+ if (sysibx) mail_close (sysibx);
+ MM_NOCRITICAL (stream); /* release critical */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ LOCAL->lastsnarf = time (0);/* note time of last snarf */
+ }
+}
+
+/* Tenex mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long tenex_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ time_t tp[2];
+ struct stat sbuf;
+ off_t pos = 0;
+ int ld;
+ unsigned long i = 1;
+ unsigned long j,k,m,recent;
+ unsigned long n = 0;
+ unsigned long delta = 0;
+ char lock[MAILTMPLEN];
+ MESSAGECACHE *elt;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (!(ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ tenex_ping (stream))); /* parse sequence if given, ping stream */
+ else if (stream->rdonly) MM_LOG ("Expunge ignored on readonly mailbox",WARN);
+ else {
+ if (LOCAL->filetime && !LOCAL->shouldcheck) {
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
+ }
+ /* The cretins who designed flock() created a window of vulnerability in
+ * upgrading locks from shared to exclusive or downgrading from exclusive
+ * to shared. Rather than maintain the lock at shared status at a minimum,
+ * flock() actually *releases* the former lock. Obviously they never talked
+ * to any database guys. Fortunately, we have the parse/append permission
+ * lock. If we require this lock before going exclusive on the mailbox,
+ * another process can not sneak in and steal the exclusive mailbox lock on
+ * us, because it will block on trying to get parse/append permission first.
+ */
+ /* get exclusive parse/append permission */
+ if ((ld = lockfd (LOCAL->fd,lock,LOCK_EX)) < 0)
+ MM_LOG ("Unable to lock expunge mailbox",ERROR);
+ /* make sure see any newly-arrived messages */
+ else if (!tenex_parse (stream));
+ /* get exclusive access */
+ else if (flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH);/* recover previous lock */
+ (*bn) (BLOCK_NONE,NIL);
+ MM_LOG ("Can't expunge because mailbox is in use by another process",
+ ERROR);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+
+ else {
+ MM_CRITICAL (stream); /* go critical */
+ recent = stream->recent; /* get recent now that pinged and locked */
+ /* for each message */
+ while (i <= stream->nmsgs) {
+ /* get cache element */
+ elt = tenex_elt (stream,i);
+ /* number of bytes to smash or preserve */
+ k = elt->private.special.text.size + tenex_size (stream,i);
+ /* if need to expunge this message */
+ if (elt->deleted && (sequence ? elt->sequence : T)) {
+ /* if recent, note one less recent message */
+ if (elt->recent) --recent;
+ delta += k; /* number of bytes to delete */
+ /* notify upper levels */
+ mail_expunged (stream,i);
+ n++; /* count up one more expunged message */
+ }
+ else if (i++ && delta) {/* preserved message */
+ /* first byte to preserve */
+ j = elt->private.special.offset;
+ do { /* read from source position */
+ m = min (k,LOCAL->buflen);
+ lseek (LOCAL->fd,j,L_SET);
+ read (LOCAL->fd,LOCAL->buf,m);
+ pos = j - delta; /* write to destination position */
+ lseek (LOCAL->fd,pos,L_SET);
+ while (T) {
+ lseek (LOCAL->fd,pos,L_SET);
+ if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
+ MM_NOTIFY (stream,strerror (errno),WARN);
+ MM_DISKERROR (stream,errno,T);
+ }
+ pos += m; /* new position */
+ j += m; /* next chunk, perhaps */
+ } while (k -= m); /* until done */
+ /* note the new address of this text */
+ elt->private.special.offset -= delta;
+ }
+ /* preserved but no deleted messages */
+ else pos = elt->private.special.offset + k;
+ }
+
+ if (n) { /* truncate file after last message */
+ if (pos != (LOCAL->filesize -= delta)) {
+ sprintf (LOCAL->buf,
+ "Calculated size mismatch %lu != %lu, delta = %lu",
+ (unsigned long) pos,(unsigned long) LOCAL->filesize,delta);
+ MM_LOG (LOCAL->buf,WARN);
+ LOCAL->filesize = pos;/* fix it then */
+ }
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ sprintf (LOCAL->buf,"Expunged %lu messages",n);
+ /* output the news */
+ MM_LOG (LOCAL->buf,(long) NIL);
+ }
+ else MM_LOG ("No messages deleted, so no update needed",(long) NIL);
+ fsync (LOCAL->fd); /* force disk update */
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* reset atime to now */
+ utime (stream->mailbox,tp);
+ MM_NOCRITICAL (stream); /* release critical */
+ /* notify upper level of new mailbox size */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ (*bn) (BLOCK_FILELOCK,NIL);
+ flock (LOCAL->fd,LOCK_SH);/* allow sharers again */
+ (*bn) (BLOCK_NONE,NIL);
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ }
+ }
+ return LONGT;
+}
+
+/* Tenex mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if success, NIL if failed
+ */
+
+long tenex_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ time_t tp[2];
+ MESSAGECACHE *elt;
+ unsigned long i,j,k;
+ long ret = LONGT;
+ int fd,ld;
+ char file[MAILTMPLEN],lock[MAILTMPLEN];
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ /* make sure valid mailbox */
+ if (!tenex_isvalid (mailbox,LOCAL->buf)) switch (errno) {
+ case ENOENT: /* no such file? */
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid Tenex-format mailbox name: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a Tenex-format mailbox: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* got file? */
+ if ((fd = open (tenex_file(file,mailbox),O_RDWR,NIL)) < 0) {
+ sprintf (LOCAL->buf,"Unable to open copy mailbox: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ MM_CRITICAL (stream); /* go critical */
+ /* get exclusive parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) {
+ MM_LOG ("Unable to lock copy mailbox",ERROR);
+ MM_NOCRITICAL (stream);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
+
+ /* for each requested message */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ /* number of bytes to copy */
+ k = elt->private.special.text.size + tenex_size (stream,i);
+ do { /* read from source position */
+ j = min (k,LOCAL->buflen);
+ read (LOCAL->fd,LOCAL->buf,j);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ } while (ret && (k -= j));/* until done */
+ }
+ /* make sure all the updates take */
+ if (!(ret && (ret = !fsync (fd)))) {
+ sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ }
+ if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */
+ /* else preserve \Marked status */
+ else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
+ tp[1] = sbuf.st_mtime; /* preserve mtime */
+ utime (file,tp); /* set the times */
+ close (fd); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ MM_NOCRITICAL (stream); /* release critical */
+ /* delete all requested messages */
+ if (ret && (options & CP_MOVE)) {
+ for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = tenex_elt (stream,i))->sequence) {
+ elt->deleted = T; /* mark message deleted */
+ /* recalculate status */
+ tenex_update_status (stream,i,NIL);
+ }
+ if (!stream->rdonly) { /* make sure the update takes */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get current write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* make sure atime remains greater */
+ utime (stream->mailbox,tp);
+ }
+ }
+ if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
+ MM_LOG ("Can not return meaningful COPYUID with this mailbox format",WARN);
+ return ret;
+}
+
+/* Tenex mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long tenex_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd,ld,c;
+ char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ time_t tp[2];
+ FILE *df;
+ MESSAGECACHE elt;
+ long f;
+ unsigned long i,j,uf,size;
+ STRING *message;
+ long ret = LONGT;
+ /* default stream to prototype */
+ if (!stream) stream = user_flags (&tenexproto);
+ /* make sure valid mailbox */
+ if (!tenex_isvalid (mailbox,tmp)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (!compare_cstring (mailbox,"INBOX")) dummy_create (NIL,"mail.txt");
+ else {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ /* falls through */
+ case 0: /* merely empty file? */
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid TENEX-format mailbox name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a TENEX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get first message */
+ if (!MM_APPEND (af) (stream,data,&flags,&date,&message)) return NIL;
+
+ /* open destination mailbox */
+ if (((fd = open (tenex_file (file,mailbox),O_WRONLY|O_APPEND,NIL)) < 0) ||
+ !(df = fdopen (fd,"ab"))) {
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get parse/append permission */
+ if (flock (fd,LOCK_SH) || ((ld = lockfd (fd,lock,LOCK_EX)) < 0)) {
+ MM_LOG ("Unable to lock append mailbox",ERROR);
+ close (fd);
+ return NIL;
+ }
+ MM_CRITICAL (stream); /* go critical */
+ fstat (fd,&sbuf); /* get current file size */
+ errno = 0;
+ do { /* parse flags */
+ if (!SIZE (message)) { /* guard against zero-length */
+ MM_LOG ("Append of zero-length message",ERROR);
+ ret = NIL;
+ break;
+ }
+ f = mail_parse_flags (stream,flags,&i);
+ /* reverse bits (dontcha wish we had CIRC?) */
+ for (uf = 0; i; uf |= 1 << (29 - find_rightmost_bit (&i)));
+ if (date) { /* parse date if given */
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ ret = NIL; /* mark failure */
+ break;
+ }
+ mail_date (tmp,&elt); /* write preseved date */
+ }
+ else internal_date (tmp); /* get current date in IMAP format */
+ i = GETPOS (message); /* remember current position */
+ for (j = SIZE (message), size = 0; j; --j)
+ if (SNX (message) != '\015') ++size;
+ SETPOS (message,i); /* restore position */
+ /* write header */
+ if (fprintf (df,"%s,%lu;%010lo%02lo\n",tmp,size,uf,(unsigned long) f) < 0)
+ ret = NIL;
+ else { /* write message */
+ while (size) if ((c = 0xff & SNX (message)) != '\015') {
+ if (putc (c,df) != EOF) --size;
+ else break;
+ }
+ /* get next message */
+ if (size || !MM_APPEND (af) (stream,data,&flags,&date,&message))
+ ret = NIL;
+ }
+ } while (ret && message);
+ /* if error... */
+ if (!ret || (fflush (df) == EOF)) {
+ ftruncate (fd,sbuf.st_size);/* revert file */
+ close (fd); /* make sure fclose() doesn't corrupt us */
+ if (errno) {
+ sprintf (tmp,"Message append failed: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ ret = NIL;
+ }
+ if (ret) tp[0] = time (0) - 1;/* set atime to now-1 if successful copy */
+ /* else preserve \Marked status */
+ else tp[0] = (sbuf.st_ctime > sbuf.st_atime) ? sbuf.st_atime : time(0);
+ tp[1] = sbuf.st_mtime; /* preserve mtime */
+ utime (file,tp); /* set the times */
+ fclose (df); /* close the file */
+ unlockfd (ld,lock); /* release exclusive parse/append permission */
+ MM_NOCRITICAL (stream); /* release critical */
+ if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
+ MM_LOG ("Can not return meaningful APPENDUID with this mailbox format",
+ WARN);
+ return ret;
+}
+
+/* Internal routines */
+
+
+/* Tenex mail return internal message size in bytes
+ * Accepts: MAIL stream
+ * message #
+ * Returns: internal size of message
+ */
+
+unsigned long tenex_size (MAILSTREAM *stream,unsigned long m)
+{
+ MESSAGECACHE *elt = mail_elt (stream,m);
+ return ((m < stream->nmsgs) ? mail_elt (stream,m+1)->private.special.offset :
+ LOCAL->filesize) -
+ (elt->private.special.offset + elt->private.special.text.size);
+}
+
+
+/* Tenex mail generate file string
+ * Accepts: temporary buffer to write into
+ * mailbox name string
+ * Returns: local file string or NIL if failure
+ */
+
+char *tenex_file (char *dst,char *name)
+{
+ char tmp[MAILTMPLEN];
+ char *s = mailboxfile (dst,name);
+ /* return our standard inbox */
+ return (s && !*s) ? mailboxfile (dst,tenex_isvalid ("~/INBOX",tmp) ?
+ "~/INBOX" : "mail.txt") : s;
+}
+
+/* Tenex mail parse mailbox
+ * Accepts: MAIL stream
+ * Returns: T if parse OK
+ * NIL if failure, stream aborted
+ */
+
+long tenex_parse (MAILSTREAM *stream)
+{
+ struct stat sbuf;
+ MESSAGECACHE *elt = NIL;
+ unsigned char c,*s,*t,*x;
+ char tmp[MAILTMPLEN];
+ unsigned long i,j;
+ long curpos = LOCAL->filesize;
+ long nmsgs = stream->nmsgs;
+ long recent = stream->recent;
+ short added = NIL;
+ short silent = stream->silent;
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ if (sbuf.st_size < curpos) { /* sanity check */
+ sprintf (tmp,"Mailbox shrank from %lu to %lu!",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ stream->silent = T; /* don't pass up exists events yet */
+ while (sbuf.st_size - curpos){/* while there is stuff to parse */
+ /* get to that position in the file */
+ lseek (LOCAL->fd,curpos,L_SET);
+ if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
+ sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
+ (unsigned long) curpos,(unsigned long) sbuf.st_size,
+ i ? strerror (errno) : "no data read");
+ MM_LOG (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
+ if (!(s = strchr (LOCAL->buf,'\012'))) {
+ sprintf (tmp,"Unable to find newline at %lu in %lu bytes, text: %s",
+ (unsigned long) curpos,i,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ *s = '\0'; /* tie off header line */
+ i = (s + 1) - LOCAL->buf; /* note start of text offset */
+ if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
+ sprintf (tmp,"Unable to parse internal header at %lu: %s",
+ (unsigned long) curpos,(char *) LOCAL->buf);
+ MM_LOG (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ *s++ = '\0'; *t++ = '\0'; /* tie off fields */
+
+ added = T; /* note that a new message was added */
+ /* swell the cache */
+ mail_exists (stream,++nmsgs);
+ /* instantiate an elt for this message */
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ elt->private.uid = ++stream->uid_last;
+ /* note file offset of header */
+ elt->private.special.offset = curpos;
+ /* in case error */
+ elt->private.special.text.size = 0;
+ /* header size not known yet */
+ elt->private.msg.header.text.size = 0;
+ x = s; /* parse the header components */
+ if (mail_parse_date (elt,LOCAL->buf) &&
+ (elt->private.msg.full.text.size = strtoul (s,(char **) &s,10)) &&
+ (!(s && *s)) && isdigit (t[0]) && isdigit (t[1]) && isdigit (t[2]) &&
+ isdigit (t[3]) && isdigit (t[4]) && isdigit (t[5]) &&
+ isdigit (t[6]) && isdigit (t[7]) && isdigit (t[8]) &&
+ isdigit (t[9]) && isdigit (t[10]) && isdigit (t[11]) && !t[12])
+ elt->private.special.text.size = i;
+ else { /* oops */
+ sprintf (tmp,"Unable to parse internal header elements at %ld: %s,%s;%s",
+ curpos,(char *) LOCAL->buf,(char *) x,(char *) t);
+ MM_LOG (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ /* make sure didn't run off end of file */
+ if ((curpos += (elt->private.msg.full.text.size + i)) > sbuf.st_size) {
+ sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
+ elt->private.special.offset,(unsigned long) curpos,
+ (unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR);
+ tenex_close (stream,NIL);
+ return NIL;
+ }
+ c = t[10]; /* remember first system flags byte */
+ t[10] = '\0'; /* tie off flags */
+ j = strtoul (t,NIL,8); /* get user flags value */
+ t[10] = c; /* restore first system flags byte */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ /* calculate system flags */
+ if ((j = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T;
+ if (j & fDELETED) elt->deleted = T;
+ if (j & fFLAGGED) elt->flagged = T;
+ if (j & fANSWERED) elt->answered = T;
+ if (j & fDRAFT) elt->draft = T;
+ if (!(j & fOLD)) { /* newly arrived message? */
+ elt->recent = T;
+ recent++; /* count up a new recent message */
+ /* mark it as old */
+ tenex_update_status (stream,nmsgs,NIL);
+ }
+ }
+ fsync (LOCAL->fd); /* make sure all the fOLD flags take */
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
+ LOCAL->filetime = sbuf.st_mtime;
+ if (added && !stream->rdonly){/* make sure atime updated */
+ time_t tp[2];
+ tp[0] = time (0);
+ tp[1] = LOCAL->filetime;
+ utime (stream->mailbox,tp);
+ }
+ stream->silent = silent; /* can pass up events now */
+ mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
+ mail_recent (stream,recent); /* and of change in recent messages */
+ return LONGT; /* return the winnage */
+}
+
+/* Tenex get cache element with status updating from file
+ * Accepts: MAIL stream
+ * message number
+ * Returns: cache element
+ */
+
+MESSAGECACHE *tenex_elt (MAILSTREAM *stream,unsigned long msgno)
+{
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ struct { /* old flags */
+ unsigned int seen : 1;
+ unsigned int deleted : 1;
+ unsigned int flagged : 1;
+ unsigned int answered : 1;
+ unsigned int draft : 1;
+ unsigned long user_flags;
+ } old;
+ old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
+ old.answered = elt->answered; old.draft = elt->draft;
+ old.user_flags = elt->user_flags;
+ tenex_read_flags (stream,elt);
+ if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
+ (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
+ (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
+ MM_FLAGS (stream,msgno); /* let top level know */
+ return elt;
+}
+
+/* Tenex read flags from file
+ * Accepts: MAIL stream
+ * Returns: cache element
+ */
+
+void tenex_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ unsigned long i,j;
+ /* noop if readonly and have valid flags */
+ if (stream->rdonly && elt->valid) return;
+ /* set the seek pointer */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 13,L_SET);
+ /* read the new flags */
+ if (read (LOCAL->fd,LOCAL->buf,12) < 0) {
+ sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
+ fatal (LOCAL->buf);
+ }
+ /* calculate system flags */
+ i = (((LOCAL->buf[10]-'0') * 8) + LOCAL->buf[11]-'0');
+ elt->seen = i & fSEEN ? T : NIL; elt->deleted = i & fDELETED ? T : NIL;
+ elt->flagged = i & fFLAGGED ? T : NIL;
+ elt->answered = i & fANSWERED ? T : NIL; elt->draft = i & fDRAFT ? T : NIL;
+ LOCAL->buf[10] = '\0'; /* tie off flags */
+ j = strtoul(LOCAL->buf,NIL,8);/* get user flags value */
+ /* set up all valid user flags (reversed!) */
+ while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
+ stream->user_flags[i]) elt->user_flags |= 1 << i;
+ elt->valid = T; /* have valid flags now */
+}
+
+/* Tenex update status string
+ * Accepts: MAIL stream
+ * message number
+ * flag saying whether or not to sync
+ */
+
+void tenex_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag)
+{
+ time_t tp[2];
+ struct stat sbuf;
+ MESSAGECACHE *elt = mail_elt (stream,msgno);
+ unsigned long j,k = 0;
+ /* readonly */
+ if (stream->rdonly || !elt->valid) tenex_read_flags (stream,elt);
+ else { /* readwrite */
+ j = elt->user_flags; /* get user flags */
+ /* reverse bits (dontcha wish we had CIRC?) */
+ while (j) k |= 1 << (29 - find_rightmost_bit (&j));
+ /* print new flag string */
+ sprintf (LOCAL->buf,"%010lo%02o",k,(unsigned)
+ (fOLD + (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
+ (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
+ (fDRAFT * elt->draft)));
+ /* get to that place in the file */
+ lseek (LOCAL->fd,(off_t) elt->private.special.offset +
+ elt->private.special.text.size - 13,L_SET);
+ /* write new flags */
+ write (LOCAL->fd,LOCAL->buf,12);
+ if (syncflag) { /* sync if requested */
+ fsync (LOCAL->fd);
+ fstat (LOCAL->fd,&sbuf); /* get new write time */
+ tp[1] = LOCAL->filetime = sbuf.st_mtime;
+ tp[0] = time (0); /* make sure read is later */
+ utime (stream->mailbox,tp);
+ }
+ }
+}
+
+/* Tenex locate header for a message
+ * Accepts: MAIL stream
+ * message number
+ * pointer to returned header size
+ * Returns: position of header in file
+ */
+
+unsigned long tenex_hdrpos (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *size)
+{
+ unsigned long siz;
+ long i = 0;
+ char c = '\0';
+ char *s = NIL;
+ MESSAGECACHE *elt = tenex_elt (stream,msgno);
+ unsigned long ret = elt->private.special.offset +
+ elt->private.special.text.size;
+ unsigned long msiz = tenex_size (stream,msgno);
+ /* is header size known? */
+ if (!(*size = elt->private.msg.header.text.size)) {
+ lseek (LOCAL->fd,ret,L_SET);/* get to header position */
+ /* search message for LF LF */
+ for (siz = 0; siz < msiz; siz++) {
+ if (--i <= 0) /* read another buffer as necessary */
+ read (LOCAL->fd,s = LOCAL->buf,i = min (msiz-siz,(long) MAILTMPLEN));
+ /* two newline sequence? */
+ if ((c == '\012') && (*s == '\012')) {
+ /* yes, note for later */
+ elt->private.msg.header.text.size = (*size = siz + 1);
+
+ return ret; /* return to caller */
+ }
+ else c = *s++; /* next character */
+ }
+ /* header consumes entire message */
+ elt->private.msg.header.text.size = *size = msiz;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/unix/truncate.c b/imap/src/osdep/unix/truncate.c
new file mode 100644
index 00000000..3dde1072
--- /dev/null
+++ b/imap/src/osdep/unix/truncate.c
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Truncate a file
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 30 June 1994
+ * Last Edited: 30 August 2006
+ */
+
+/* Emulator for ftruncate() call
+ * Accepts: file descriptor
+ * length
+ * Returns: 0 if success, -1 if failure
+ */
+
+int ftruncate (int fd,off_t length)
+{
+ struct flock fb;
+ fb.l_whence = 0;
+ fb.l_len = 0;
+ fb.l_start = length;
+ fb.l_type = F_WRLCK; /* write lock on file space */
+ return fcntl (fd,F_FREESP,&fb);
+}
diff --git a/imap/src/osdep/unix/tz_bsd.c b/imap/src/osdep/unix/tz_bsd.c
new file mode 100644
index 00000000..75ee624c
--- /dev/null
+++ b/imap/src/osdep/unix/tz_bsd.c
@@ -0,0 +1,38 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: BSD-style Time Zone String
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 30 August 1994
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Append local timezone name
+ * Accepts: destination string
+ */
+
+void rfc822_timezone (char *s,void *t)
+{
+ /* append timezone from tm struct */
+ sprintf (s + strlen (s)," (%.50s)",((struct tm *) t)->tm_zone);
+}
diff --git a/imap/src/osdep/unix/tz_nul.c b/imap/src/osdep/unix/tz_nul.c
new file mode 100644
index 00000000..d2dd2895
--- /dev/null
+++ b/imap/src/osdep/unix/tz_nul.c
@@ -0,0 +1,36 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Null Time Zone String (unknown)
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 30 August 1994
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Append local timezone name
+ * Accepts: destination string
+ */
+
+void rfc822_timezone (char *s,void *t)
+{
+}
diff --git a/imap/src/osdep/unix/tz_sv4.c b/imap/src/osdep/unix/tz_sv4.c
new file mode 100644
index 00000000..409adf14
--- /dev/null
+++ b/imap/src/osdep/unix/tz_sv4.c
@@ -0,0 +1,39 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: SVR4-style Time Zone String
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 30 August 1994
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Append local timezone name
+ * Accepts: destination string
+ */
+
+void rfc822_timezone (char *s,void *t)
+{
+ tzset (); /* get timezone from TZ environment stuff */
+ sprintf (s + strlen (s)," (%.50s)",
+ tzname[daylight ? (((struct tm *) t)->tm_isdst > 0) : 0]);
+}
diff --git a/imap/src/osdep/unix/unix.c b/imap/src/osdep/unix/unix.c
new file mode 100644
index 00000000..be3c437b
--- /dev/null
+++ b/imap/src/osdep/unix/unix.c
@@ -0,0 +1,2708 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX mail routines
+ *
+ * Author: Mark Crispin
+ * UW Technology
+ * University of Washington
+ * Seattle, WA 98195
+ * Internet: MRC@Washington.EDU
+ *
+ * Date: 20 December 1989
+ * Last Edited: 27 March 2008
+ */
+
+
+/* DEDICATION
+ *
+ * This file is dedicated to my dog, Unix, also known as Yun-chan and
+ * Unix J. Terwilliker Jehosophat Aloysius Monstrosity Animal Beast. Unix
+ * passed away at the age of 11 1/2 on September 14, 1996, 12:18 PM PDT, after
+ * a two-month bout with cirrhosis of the liver.
+ *
+ * He was a dear friend, and I miss him terribly.
+ *
+ * Lift a leg, Yunie. Luv ya forever!!!!
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include <signal.h>
+#include "mail.h"
+#include "osdep.h"
+#include <time.h>
+#include <sys/stat.h>
+#include "unix.h"
+#include "pseudo.h"
+#include "fdstring.h"
+#include "misc.h"
+#include "dummy.h"
+
+/* UNIX I/O stream local data */
+
+typedef struct unix_local {
+ unsigned int dirty : 1; /* disk copy needs updating */
+ unsigned int ddirty : 1; /* double-dirty, ping becomes checkpoint */
+ unsigned int pseudo : 1; /* uses a pseudo message */
+ unsigned int appending : 1; /* don't mark new messages as old */
+ int fd; /* mailbox file descriptor */
+ int ld; /* lock file descriptor */
+ char *lname; /* lock file name */
+ off_t filesize; /* file size parsed */
+ time_t filetime; /* last file time */
+ time_t lastsnarf; /* last snarf time (for mbox driver) */
+ unsigned char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ unsigned long uid; /* current text uid */
+ SIZEDTEXT text; /* current text */
+ unsigned long textlen; /* current text length */
+ char *line; /* returned line */
+ char *linebuf; /* line readin buffer */
+ unsigned long linebuflen; /* current line readin buffer length */
+} UNIXLOCAL;
+
+
+/* Convenient access to local data */
+
+#define LOCAL ((UNIXLOCAL *) stream->local)
+
+
+/* UNIX protected file structure */
+
+typedef struct unix_file {
+ MAILSTREAM *stream; /* current stream */
+ off_t curpos; /* current file position */
+ off_t protect; /* protected position */
+ off_t filepos; /* current last written file position */
+ char *buf; /* overflow buffer */
+ size_t buflen; /* current overflow buffer length */
+ char *bufpos; /* current buffer position */
+} UNIXFILE;
+
+/* Function prototypes */
+
+DRIVER *unix_valid (char *name);
+long unix_isvalid_fd (int fd);
+void *unix_parameters (long function,void *value);
+void unix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void unix_list (MAILSTREAM *stream,char *ref,char *pat);
+void unix_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long unix_create (MAILSTREAM *stream,char *mailbox);
+long unix_delete (MAILSTREAM *stream,char *mailbox);
+long unix_rename (MAILSTREAM *stream,char *old,char *newname);
+MAILSTREAM *unix_open (MAILSTREAM *stream);
+void unix_close (MAILSTREAM *stream,long options);
+char *unix_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags);
+long unix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+char *unix_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
+ unsigned long *length,long flags);
+void unix_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+long unix_ping (MAILSTREAM *stream);
+void unix_check (MAILSTREAM *stream);
+long unix_expunge (MAILSTREAM *stream,char *sequence,long options);
+long unix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long unix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+int unix_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg);
+int unix_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set);
+
+void unix_abort (MAILSTREAM *stream);
+char *unix_file (char *dst,char *name);
+int unix_lock (char *file,int flags,int mode,DOTLOCK *lock,int op);
+void unix_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock);
+int unix_parse (MAILSTREAM *stream,DOTLOCK *lock,int op);
+char *unix_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size);
+unsigned long unix_pseudo (MAILSTREAM *stream,char *hdr);
+unsigned long unix_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
+ unsigned long uid,long flag);
+long unix_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock,
+ long flags);
+long unix_extend (MAILSTREAM *stream,unsigned long size);
+void unix_write (UNIXFILE *f,char *s,unsigned long i);
+void unix_phys_write (UNIXFILE *f,char *buf,size_t size);
+
+/* mbox mail routines */
+
+/* Function prototypes */
+
+DRIVER *mbox_valid (char *name);
+long mbox_create (MAILSTREAM *stream,char *mailbox);
+long mbox_delete (MAILSTREAM *stream,char *mailbox);
+long mbox_rename (MAILSTREAM *stream,char *old,char *newname);
+long mbox_status (MAILSTREAM *stream,char *mbx,long flags);
+MAILSTREAM *mbox_open (MAILSTREAM *stream);
+long mbox_ping (MAILSTREAM *stream);
+void mbox_check (MAILSTREAM *stream);
+long mbox_expunge (MAILSTREAM *stream,char *sequence,long options);
+long mbox_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+
+/* UNIX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER unixdriver = {
+ "unix", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_LOCKING|DR_NONEWMAILRONLY|DR_XPOINT,
+ (DRIVER *) NIL, /* next driver */
+ unix_valid, /* mailbox is valid for us */
+ unix_parameters, /* manipulate parameters */
+ unix_scan, /* scan mailboxes */
+ unix_list, /* list mailboxes */
+ unix_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ unix_create, /* create mailbox */
+ unix_delete, /* delete mailbox */
+ unix_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ unix_open, /* open mailbox */
+ unix_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ unix_header, /* fetch message header */
+ unix_text, /* fetch message text */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ unix_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ unix_ping, /* ping mailbox to see if still alive */
+ unix_check, /* check for new messages */
+ unix_expunge, /* expunge deleted messages */
+ unix_copy, /* copy messages to another mailbox */
+ unix_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM unixproto = {&unixdriver};
+
+ /* driver parameters */
+static long unix_fromwidget = T;
+
+/* UNIX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *unix_valid (char *name)
+{
+ int fd;
+ DRIVER *ret = NIL;
+ char *t,file[MAILTMPLEN];
+ struct stat sbuf;
+ time_t tp[2];
+ errno = EINVAL; /* assume invalid argument */
+ /* must be non-empty file */
+ if ((t = dummy_file (file,name)) && !stat (t,&sbuf)) {
+ if (!sbuf.st_size)errno = 0;/* empty file */
+ else if ((fd = open (file,O_RDONLY,NIL)) >= 0) {
+ /* OK if mailbox format good */
+ if (unix_isvalid_fd (fd)) ret = &unixdriver;
+ else errno = -1; /* invalid format */
+ close (fd); /* close the file */
+ /* \Marked status? */
+ if ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) {
+ tp[0] = sbuf.st_atime; /* yes, preserve atime and mtime */
+ tp[1] = sbuf.st_mtime;
+ utime (file,tp); /* set the times */
+ }
+ }
+ }
+ return ret; /* return what we should */
+}
+
+/* UNIX mail test for valid mailbox
+ * Accepts: file descriptor
+ * scratch buffer
+ * Returns: T if valid, NIL otherwise
+ */
+
+long unix_isvalid_fd (int fd)
+{
+ int zn;
+ int ret = NIL;
+ char tmp[MAILTMPLEN],*s,*t,c = '\n';
+ memset (tmp,'\0',MAILTMPLEN);
+ if (read (fd,tmp,MAILTMPLEN-1) >= 0) {
+ for (s = tmp; (*s == '\r') || (*s == '\n') || (*s == ' ') || (*s == '\t');)
+ c = *s++;
+ if (c == '\n') VALID (s,t,ret,zn);
+ }
+ return ret; /* return what we should */
+}
+
+
+/* UNIX manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *unix_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_INBOXPATH:
+ if (value) ret = dummy_file ((char *) value,"INBOX");
+ break;
+ case SET_FROMWIDGET:
+ unix_fromwidget = (long) value;
+ case GET_FROMWIDGET:
+ ret = (void *) unix_fromwidget;
+ break;
+ }
+ return ret;
+}
+
+/* UNIX mail scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void unix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ if (stream) dummy_scan (NIL,ref,pat,contents);
+}
+
+
+/* UNIX mail list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void unix_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_list (NIL,ref,pat);
+}
+
+
+/* UNIX mail list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void unix_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ if (stream) dummy_lsub (NIL,ref,pat);
+}
+
+/* UNIX mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long unix_create (MAILSTREAM *stream,char *mailbox)
+{
+ char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN];
+ long ret = NIL;
+ int i,fd;
+ time_t ti = time (0);
+ if (!(s = dummy_file (mbx,mailbox))) {
+ sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
+ MM_LOG (tmp,ERROR);
+ }
+ /* create underlying file */
+ else if (dummy_create_path (stream,s,get_dir_protection (mailbox))) {
+ /* done if dir-only or whiner */
+ if (((s = strrchr (s,'/')) && !s[1]) ||
+ mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) ret = T;
+ else if ((fd = open (mbx,O_WRONLY,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0) {
+ sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ else { /* initialize header */
+ memset (tmp,'\0',MAILTMPLEN);
+ sprintf (tmp,"From %s %sDate: ",pseudo_from,ctime (&ti));
+ rfc822_fixed_date (s = tmp + strlen (tmp));
+ /* write the pseudo-header */
+ sprintf (s += strlen (s),
+ "\nFrom: %s <%s@%s>\nSubject: %s\nX-IMAP: %010lu 0000000000",
+ pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
+ (unsigned long) ti);
+ for (i = 0; i < NUSERFLAGS; ++i) if (default_user_flag (i))
+ sprintf (s += strlen (s)," %s",default_user_flag (i));
+ sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n\n",pseudo_msg);
+ if (write (fd,tmp,strlen (tmp)) > 0) ret = T;
+ else {
+ sprintf (tmp,"Can't initialize mailbox node %.80s: %s",mbx,
+ strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unlink (mbx); /* delete the file */
+ }
+ close (fd); /* close file */
+ }
+ }
+ /* set proper protections */
+ return ret ? set_mbx_protections (mailbox,mbx) : NIL;
+}
+
+/* UNIX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long unix_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return unix_rename (stream,mailbox,NIL);
+}
+
+
+/* UNIX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long unix_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ long ret = NIL;
+ char c,*s = NIL;
+ char tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
+ DOTLOCK lockx;
+ int fd,ld;
+ long i;
+ struct stat sbuf;
+ MM_CRITICAL (stream); /* get the c-client lock */
+ if (!dummy_file (file,old) ||
+ (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
+ ((s = strrchr (tmp,'/')) && !s[1]))))
+ sprintf (tmp,newname ?
+ "Can't rename mailbox %.80s to %.80s: invalid name" :
+ "Can't delete mailbox %.80s: invalid name",
+ old,newname);
+ /* lock out other c-clients */
+ else if ((ld = lockname (lock,file,LOCK_EX|LOCK_NB,&i)) < 0)
+ sprintf (tmp,"Mailbox %.80s is in use by another process",old);
+
+ else {
+ if ((fd = unix_lock (file,O_RDWR,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lockx,LOCK_EX)) < 0)
+ sprintf (tmp,"Can't lock mailbox %.80s: %s",old,strerror (errno));
+ else {
+ if (newname) { /* want rename? */
+ /* found superior to destination name? */
+ if (s = strrchr (s,'/')) {
+ c = *++s; /* remember first character of inferior */
+ *s = '\0'; /* tie off to get just superior */
+ /* name doesn't exist, create it */
+ if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
+ !dummy_create_path (stream,tmp,get_dir_protection (newname))) {
+ unix_unlock (fd,NIL,&lockx);
+ unix_unlock (ld,NIL,NIL);
+ unlink (lock);
+ MM_NOCRITICAL (stream);
+ return ret; /* return success or failure */
+ }
+ *s = c; /* restore full name */
+ }
+ if (rename (file,tmp))
+ sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
+ strerror (errno));
+ else ret = T; /* set success */
+ }
+ else if (unlink (file))
+ sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
+ else ret = T; /* set success */
+ unix_unlock (fd,NIL,&lockx);
+ }
+ unix_unlock (ld,NIL,NIL); /* flush the lock */
+ unlink (lock);
+ }
+ MM_NOCRITICAL (stream); /* no longer critical */
+ if (!ret) MM_LOG (tmp,ERROR); /* log error */
+ return ret; /* return success or failure */
+}
+
+/* UNIX mail open
+ * Accepts: Stream to open
+ * Returns: Stream on success, NIL on failure
+ */
+
+MAILSTREAM *unix_open (MAILSTREAM *stream)
+{
+ long i;
+ int fd;
+ char tmp[MAILTMPLEN];
+ DOTLOCK lock;
+ long retry;
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return user_flags (&unixproto);
+ retry = stream->silent ? 1 : KODRETRY;
+ if (stream->local) fatal ("unix recycle stream");
+ stream->local = memset (fs_get (sizeof (UNIXLOCAL)),0,sizeof (UNIXLOCAL));
+ /* note if an INBOX or not */
+ stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
+ /* canonicalize the stream mailbox name */
+ if (!dummy_file (tmp,stream->mailbox)) {
+ sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* flush old name */
+ fs_give ((void **) &stream->mailbox);
+ /* save canonical name */
+ stream->mailbox = cpystr (tmp);
+ LOCAL->fd = LOCAL->ld = -1; /* no file or state locking yet */
+ LOCAL->buf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->buflen = CHUNKSIZE - 1;
+ LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE);
+ LOCAL->text.size = CHUNKSIZE - 1;
+ LOCAL->linebuf = (char *) fs_get (CHUNKSIZE);
+ LOCAL->linebuflen = CHUNKSIZE - 1;
+ stream->sequence++; /* bump sequence number */
+
+ /* make lock for read/write access */
+ if (!stream->rdonly) while (retry) {
+ /* try to lock file */
+ if ((fd = lockname (tmp,stream->mailbox,LOCK_EX|LOCK_NB,&i)) < 0) {
+ /* suppressing kiss-of-death? */
+ if (stream->nokod) retry = 0;
+ /* no, first time through? */
+ else if (retry-- == KODRETRY) {
+ /* learned other guy's PID and can signal? */
+ if (i && !kill ((int) i,SIGUSR2)) {
+ sprintf (tmp,"Trying to get mailbox lock from process %ld",i);
+ MM_LOG (tmp,WARN);
+ }
+ else retry = 0; /* give up */
+ }
+ if (!stream->silent) { /* nothing if silent stream */
+ if (retry) sleep (1); /* wait a second before trying again */
+ else MM_LOG ("Mailbox is open by another process, access is readonly",
+ WARN);
+ }
+ }
+ else { /* got the lock, nobody else can alter state */
+ LOCAL->ld = fd; /* note lock's fd and name */
+ LOCAL->lname = cpystr (tmp);
+ /* make sure mode OK (don't use fchmod()) */
+ chmod (LOCAL->lname,(long) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
+ if (stream->silent) i = 0;/* silent streams won't accept KOD */
+ else { /* note our PID in the lock */
+ sprintf (tmp,"%d",getpid ());
+ write (fd,tmp,(i = strlen (tmp))+1);
+ }
+ ftruncate (fd,i); /* make sure tied off */
+ fsync (fd); /* make sure it's available */
+ retry = 0; /* no more need to try */
+ }
+ }
+
+ /* parse mailbox */
+ stream->nmsgs = stream->recent = 0;
+ /* will we be able to get write access? */
+ if ((LOCAL->ld >= 0) && access (stream->mailbox,W_OK) && (errno == EACCES)) {
+ MM_LOG ("Can't get write access to mailbox, access is readonly",WARN);
+ flock (LOCAL->ld,LOCK_UN); /* release the lock */
+ close (LOCAL->ld); /* close the lock file */
+ LOCAL->ld = -1; /* no more lock fd */
+ unlink (LOCAL->lname); /* delete it */
+ }
+ /* reset UID validity */
+ stream->uid_validity = stream->uid_last = 0;
+ if (stream->silent && !stream->rdonly && (LOCAL->ld < 0))
+ unix_abort (stream); /* abort if can't get RW silent stream */
+ /* parse mailbox */
+ else if (unix_parse (stream,&lock,LOCK_SH)) {
+ unix_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream); /* done with critical */
+ }
+ if (!LOCAL) return NIL; /* failure if stream died */
+ /* make sure upper level knows readonly */
+ stream->rdonly = (LOCAL->ld < 0);
+ /* notify about empty mailbox */
+ if (!(stream->nmsgs || stream->silent)) MM_LOG ("Mailbox is empty",NIL);
+ if (!stream->rdonly) { /* flags stick if readwrite */
+ stream->perm_seen = stream->perm_deleted =
+ stream->perm_flagged = stream->perm_answered = stream->perm_draft = T;
+ if (!stream->uid_nosticky) {/* users with lives get permanent keywords */
+ stream->perm_user_flags = 0xffffffff;
+ /* and maybe can create them too! */
+ stream->kwd_create = stream->user_flags[NUSERFLAGS-1] ? NIL : T;
+ }
+ }
+ return stream; /* return stream alive to caller */
+}
+
+
+/* UNIX mail close
+ * Accepts: MAIL stream
+ * close options
+ */
+
+void unix_close (MAILSTREAM *stream,long options)
+{
+ int silent = stream->silent;
+ stream->silent = T; /* go silent */
+ /* expunge if requested */
+ if (options & CL_EXPUNGE) unix_expunge (stream,NIL,NIL);
+ /* else dump final checkpoint */
+ else if (LOCAL->dirty) unix_check (stream);
+ stream->silent = silent; /* restore old silence state */
+ unix_abort (stream); /* now punt the file and local data */
+}
+
+/* UNIX mail fetch message header
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned header text length
+ * option flags
+ * Returns: message header in RFC822 format
+ */
+
+ /* lines to filter from header */
+static STRINGLIST *unix_hlines = NIL;
+
+char *unix_header (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length,long flags)
+{
+ MESSAGECACHE *elt;
+ unsigned char *s,*t,*tl;
+ *length = 0; /* default to empty */
+ if (flags & FT_UID) return "";/* UID call "impossible" */
+ elt = mail_elt (stream,msgno);/* get cache */
+ if (!unix_hlines) { /* once only code */
+ STRINGLIST *lines = unix_hlines = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "Status"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-Status"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-Keywords"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-UID"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-IMAP"));
+ lines = lines->next = mail_newstringlist ();
+ lines->text.size = strlen ((char *) (lines->text.data =
+ (unsigned char *) "X-IMAPbase"));
+ }
+ /* go to header position */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.msg.header.offset,L_SET);
+
+ if (flags & FT_INTERNAL) { /* initial data OK? */
+ if (elt->private.msg.header.text.size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
+ elt->private.msg.header.text.size) + 1);
+ }
+ /* read message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.header.text.size);
+ /* got text, tie off string */
+ LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0';
+ /* squeeze out CRs (in case from PC) */
+ for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
+ if (*t != '\r') *s++ = *t;
+ *s = '\0';
+ *length = s - LOCAL->buf; /* adjust length */
+ }
+ else { /* need to make a CRLF version */
+ read (LOCAL->fd,s = (char *) fs_get (elt->private.msg.header.text.size+1),
+ elt->private.msg.header.text.size);
+ /* tie off string, and convert to CRLF */
+ s[elt->private.msg.header.text.size] = '\0';
+ *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s,
+ elt->private.msg.header.text.size);
+ fs_give ((void **) &s); /* free readin buffer */
+ /* squeeze out spurious CRs */
+ for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
+ if ((*t != '\r') || (t[1] == '\n')) *s++ = *t;
+ *s = '\0';
+ *length = s - LOCAL->buf; /* adjust length */
+ }
+ *length = mail_filter (LOCAL->buf,*length,unix_hlines,FT_NOT);
+ return (char *) LOCAL->buf; /* return processed copy */
+}
+
+/* UNIX mail fetch message text
+ * Accepts: MAIL stream
+ * message # to fetch
+ * pointer to returned stringstruct
+ * option flags
+ * Returns: T on success, NIL if failure
+ */
+
+long unix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ char *s;
+ unsigned long i;
+ MESSAGECACHE *elt;
+ /* UID call "impossible" */
+ if (flags & FT_UID) return NIL;
+ elt = mail_elt (stream,msgno);/* get cache element */
+ /* if message not seen */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ /* mark message seen and dirty */
+ elt->seen = elt->private.dirty = LOCAL->dirty = T;
+ MM_FLAGS (stream,msgno);
+ }
+ s = unix_text_work (stream,elt,&i,flags);
+ INIT (bs,mail_string,s,i); /* set up stringstruct */
+ return T; /* success */
+}
+
+/* UNIX mail fetch message text worker routine
+ * Accepts: MAIL stream
+ * message cache element
+ * pointer to returned header text length
+ * option flags
+ */
+
+char *unix_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
+ unsigned long *length,long flags)
+{
+ FDDATA d;
+ STRING bs;
+ unsigned char c,*s,*t,*tl,tmp[CHUNKSIZE];
+ /* go to text position */
+ lseek (LOCAL->fd,elt->private.special.offset +
+ elt->private.msg.text.offset,L_SET);
+ if (flags & FT_INTERNAL) { /* initial data OK? */
+ if (elt->private.msg.text.text.size > LOCAL->buflen) {
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
+ elt->private.msg.text.text.size) + 1);
+ }
+ /* read message */
+ read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size);
+ /* got text, tie off string */
+ LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0';
+ /* squeeze out CRs (in case from PC) */
+ for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
+ if (*t != '\r') *s++ = *t;
+ *s = '\0';
+ *length = s - LOCAL->buf; /* adjust length */
+ return (char *) LOCAL->buf;
+ }
+
+ /* have it cached already? */
+ if (elt->private.uid != LOCAL->uid) {
+ /* not cached, cache it now */
+ LOCAL->uid = elt->private.uid;
+ /* is buffer big enough? */
+ if (elt->rfc822_size > LOCAL->text.size) {
+ /* excessively conservative, but the right thing is too hard to do */
+ fs_give ((void **) &LOCAL->text.data);
+ LOCAL->text.data = (unsigned char *)
+ fs_get ((LOCAL->text.size = elt->rfc822_size) + 1);
+ }
+ d.fd = LOCAL->fd; /* yes, set up file descriptor */
+ d.pos = elt->private.special.offset + elt->private.msg.text.offset;
+ d.chunk = tmp; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* file chunk size */
+ INIT (&bs,fd_string,&d,elt->private.msg.text.text.size);
+ for (s = (char *) LOCAL->text.data; SIZE (&bs);) switch (c = SNX (&bs)) {
+ case '\r': /* carriage return seen */
+ break;
+ case '\n':
+ *s++ = '\r'; /* insert a CR */
+ default:
+ *s++ = c; /* copy characters */
+ }
+ *s = '\0'; /* tie off buffer */
+ /* calculate length of cached data */
+ LOCAL->textlen = s - LOCAL->text.data;
+ }
+ *length = LOCAL->textlen; /* return from cache */
+ return (char *) LOCAL->text.data;
+}
+
+/* UNIX per-message modify flag
+ * Accepts: MAIL stream
+ * message cache element
+ */
+
+void unix_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
+{
+ /* only after finishing */
+ if (elt->valid) elt->private.dirty = LOCAL->dirty = T;
+}
+
+
+/* UNIX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long unix_ping (MAILSTREAM *stream)
+{
+ DOTLOCK lock;
+ struct stat sbuf;
+ long reparse;
+ /* big no-op if not readwrite */
+ if (LOCAL && (LOCAL->ld >= 0) && !stream->lock) {
+ if (stream->rdonly) { /* does he want to give up readwrite? */
+ /* checkpoint if we changed something */
+ if (LOCAL->dirty) unix_check (stream);
+ flock (LOCAL->ld,LOCK_UN);/* release readwrite lock */
+ close (LOCAL->ld); /* close the readwrite lock file */
+ LOCAL->ld = -1; /* no more readwrite lock fd */
+ unlink (LOCAL->lname); /* delete the readwrite lock file */
+ }
+ else { /* see if need to reparse */
+ if (!(reparse = (long) mail_parameters (NIL,GET_NETFSSTATBUG,NIL))) {
+ /* get current mailbox size */
+ if (LOCAL->fd >= 0) fstat (LOCAL->fd,&sbuf);
+ else if (stat (stream->mailbox,&sbuf)) {
+ sprintf (LOCAL->buf,"Mailbox stat failed, aborted: %s",
+ strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ unix_abort (stream);
+ return NIL;
+ }
+ reparse = (sbuf.st_size != LOCAL->filesize);
+ }
+ /* parse if mailbox changed */
+ if ((LOCAL->ddirty || reparse) && unix_parse (stream,&lock,LOCK_EX)) {
+ /* force checkpoint if double-dirty */
+ if (LOCAL->ddirty) unix_rewrite (stream,NIL,&lock,NIL);
+ /* unlock mailbox */
+ else unix_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* and stream */
+ MM_NOCRITICAL (stream); /* done with critical */
+ }
+ }
+ }
+ return LOCAL ? LONGT : NIL; /* return if still alive */
+}
+
+/* UNIX mail check mailbox
+ * Accepts: MAIL stream
+ */
+
+void unix_check (MAILSTREAM *stream)
+{
+ DOTLOCK lock;
+ /* parse and lock mailbox */
+ if (LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
+ unix_parse (stream,&lock,LOCK_EX)) {
+ /* any unsaved changes? */
+ if (LOCAL->dirty && unix_rewrite (stream,NIL,&lock,NIL)) {
+ if (!stream->silent) MM_LOG ("Checkpoint completed",NIL);
+ }
+ /* no checkpoint needed, just unlock */
+ else unix_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* unlock the stream */
+ MM_NOCRITICAL (stream); /* done with critical */
+ }
+}
+
+
+/* UNIX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long unix_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret;
+ unsigned long i;
+ DOTLOCK lock;
+ char *msg = NIL;
+ /* parse and lock mailbox */
+ if (ret = (sequence ? ((options & EX_UID) ?
+ mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) : LONGT) &&
+ LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
+ unix_parse (stream,&lock,LOCK_EX)) {
+ /* check expunged messages if not dirty */
+ for (i = 1; !LOCAL->dirty && (i <= stream->nmsgs); i++) {
+ MESSAGECACHE *elt = mail_elt (stream,i);
+ if (mail_elt (stream,i)->deleted) LOCAL->dirty = T;
+ }
+ if (!LOCAL->dirty) { /* not dirty and no expunged messages */
+ unix_unlock (LOCAL->fd,stream,&lock);
+ msg = "No messages deleted, so no update needed";
+ }
+ else if (unix_rewrite (stream,&i,&lock,sequence ? LONGT : NIL)) {
+ if (i) sprintf (msg = LOCAL->buf,"Expunged %lu messages",i);
+ else msg = "Mailbox checkpointed, but no messages expunged";
+ }
+ /* rewrite failed */
+ else unix_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* unlock the stream */
+ MM_NOCRITICAL (stream); /* done with critical */
+ if (msg && !stream->silent) MM_LOG (msg,NIL);
+ }
+ else if (!stream->silent) MM_LOG("Expunge ignored on readonly mailbox",WARN);
+ return ret;
+}
+
+/* UNIX mail copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * copy options
+ * Returns: T if copy successful, else NIL
+ */
+
+long unix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ struct stat sbuf;
+ int fd;
+ char *s,file[MAILTMPLEN];
+ DOTLOCK lock;
+ time_t tp[2];
+ unsigned long i,j;
+ MESSAGECACHE *elt;
+ long ret = T;
+ mailproxycopy_t pc =
+ (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
+ copyuid_t cu = (copyuid_t) (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ?
+ NIL : mail_parameters (NIL,GET_COPYUID,NIL));
+ SEARCHSET *source = cu ? mail_newsearchset () : NIL;
+ SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
+ MAILSTREAM *tstream = NIL;
+ DRIVER *d;
+ for (d = (DRIVER *) mail_parameters (NIL,GET_DRIVERS,NIL);
+ (d && strcmp (d->name,"mbox") && !(d->flags & DR_DISABLE));
+ d = d->next); /* see if mbox driver active */
+ if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence))) return NIL;
+ /* make sure destination is valid */
+ if (!((d && mbox_valid (mailbox) && (mailbox = "mbox")) ||
+ unix_valid (mailbox) || !errno))
+ switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
+ return NIL;
+ }
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ unix_create (NIL,"INBOX");/* create empty INBOX */
+ case EACCES: /* file protected */
+ sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ case EINVAL:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Invalid UNIX-format mailbox name: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ default:
+ if (pc) return (*pc) (stream,sequence,mailbox,options);
+ sprintf (LOCAL->buf,"Not a UNIX-format mailbox: %.80s",mailbox);
+ MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+
+ /* try to open rewrite for UIDPLUS */
+ if ((tstream = mail_open_work (&unixdriver,NIL,mailbox,
+ OP_SILENT|OP_NOKOD)) && tstream->rdonly)
+ tstream = mail_close (tstream);
+ if (cu && !tstream) { /* wanted a COPYUID? */
+ sprintf (LOCAL->buf,"Unable to write-open mailbox for COPYUID: %.80s",
+ mailbox);
+ MM_LOG (LOCAL->buf,WARN);
+ cu = NIL; /* don't try to do COPYUID */
+ }
+ LOCAL->buf[0] = '\0';
+ MM_CRITICAL (stream); /* go critical */
+ if ((fd = unix_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lock,LOCK_EX)) < 0) {
+ MM_NOCRITICAL (stream); /* done with critical */
+ sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);/* log the error */
+ return NIL; /* failed */
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ /* write all requested messages to mailbox */
+ for (i = 1; ret && (i <= stream->nmsgs); i++)
+ if ((elt = mail_elt (stream,i))->sequence) {
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
+ if (write (fd,LOCAL->buf,elt->private.special.text.size) < 0) ret = NIL;
+ else { /* internal header succeeded */
+ s = unix_header (stream,i,&j,FT_INTERNAL);
+ /* header size, sans trailing newline */
+ if (j && (s[j - 2] == '\n')) j--;
+ if (write (fd,s,j) < 0) ret = NIL;
+ else { /* message header succeeded */
+ j = tstream ? /* write UIDPLUS data if have readwrite */
+ unix_xstatus (stream,LOCAL->buf,elt,++(tstream->uid_last),LONGT) :
+ unix_xstatus (stream,LOCAL->buf,elt,NIL,NIL);
+ if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
+ else { /* message status succeeded */
+ s = unix_text_work (stream,elt,&j,FT_INTERNAL);
+ if ((write (fd,s,j) < 0) || (write (fd,"\n",1) < 0)) ret = NIL;
+ else if (cu) { /* need to pass back new UID? */
+ mail_append_set (source,mail_uid (stream,i));
+ mail_append_set (dest,tstream->uid_last);
+ }
+ }
+ }
+ }
+ }
+
+ if (!ret || fsync (fd)) { /* force out the update */
+ sprintf (LOCAL->buf,"Message copy failed: %s",strerror (errno));
+ ftruncate (fd,sbuf.st_size);
+ ret = NIL;
+ }
+ /* force UIDVALIDITY assignment now */
+ if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0);
+ /* return sets if doing COPYUID */
+ if (cu && ret) (*cu) (stream,mailbox,tstream->uid_validity,source,dest);
+ else { /* flush any sets we may have built */
+ mail_free_searchset (&source);
+ mail_free_searchset (&dest);
+ }
+ tp[1] = time (0); /* set mtime to now */
+ if (ret) tp[0] = tp[1] - 1; /* set atime to now-1 if successful copy */
+ else tp[0] = /* else preserve \Marked status */
+ ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
+ sbuf.st_atime : tp[1];
+ utime (file,tp); /* set the times */
+ unix_unlock (fd,NIL,&lock); /* unlock and close mailbox */
+ if (tstream) { /* update last UID if we can */
+ UNIXLOCAL *local = (UNIXLOCAL *) tstream->local;
+ local->dirty = T; /* do a rewrite */
+ local->appending = T; /* but not at the cost of marking as old */
+ tstream = mail_close (tstream);
+ }
+ /* log the error */
+ if (!ret) MM_LOG (LOCAL->buf,ERROR);
+ /* delete if requested message */
+ else if (options & CP_MOVE) for (i = 1; i <= stream->nmsgs; i++)
+ if ((elt = mail_elt (stream,i))->sequence)
+ elt->deleted = elt->private.dirty = LOCAL->dirty = T;
+ MM_NOCRITICAL (stream); /* release critical */
+ return ret;
+}
+
+/* UNIX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+#define BUFLEN 8*MAILTMPLEN
+
+long unix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ struct stat sbuf;
+ int fd;
+ unsigned long i;
+ char *flags,*date,buf[BUFLEN],tmp[MAILTMPLEN],file[MAILTMPLEN];
+ time_t tp[2];
+ FILE *sf,*df;
+ MESSAGECACHE elt;
+ DOTLOCK lock;
+ STRING *message;
+ unsigned long uidlocation = 0;
+ appenduid_t au = (appenduid_t)
+ (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ? NIL :
+ mail_parameters (NIL,GET_APPENDUID,NIL));
+ SEARCHSET *dst = au ? mail_newsearchset () : NIL;
+ long ret = LONGT;
+ MAILSTREAM *tstream = NIL;
+ if (!stream) { /* stream specified? */
+ stream = &unixproto; /* no, default stream to prototype */
+ for (i = 0; i < NUSERFLAGS && stream->user_flags[i]; ++i)
+ fs_give ((void **) &stream->user_flags[i]);
+ }
+ if (!unix_valid (mailbox)) switch (errno) {
+ case ENOENT: /* no such file? */
+ if (compare_cstring (mailbox,"INBOX")) {
+ MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
+ return NIL;
+ }
+ unix_create (NIL,"INBOX"); /* create empty INBOX */
+ case 0: /* merely empty file? */
+ tstream = stream;
+ break;
+ case EACCES: /* file protected */
+ sprintf (tmp,"Can't access destination: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ case EINVAL:
+ sprintf (tmp,"Invalid UNIX-format mailbox name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ default:
+ sprintf (tmp,"Not a UNIX-format mailbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ /* get sniffing stream for keywords */
+ else if (!(tstream = mail_open (NIL,mailbox,
+ OP_READONLY|OP_SILENT|OP_NOKOD|OP_SNIFF))) {
+ sprintf (tmp,"Unable to examine mailbox for APPEND: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+
+ /* get first message */
+ if (!MM_APPEND (af) (tstream,data,&flags,&date,&message)) return NIL;
+ if (!(sf = tmpfile ())) { /* must have scratch file */
+ sprintf (tmp,".%lx.%lx",(unsigned long) time (0),(unsigned long)getpid ());
+ if (!stat (tmp,&sbuf) || !(sf = fopen (tmp,"wb+"))) {
+ sprintf (tmp,"Unable to create scratch file: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ unlink (tmp);
+ }
+ do { /* parse date */
+ if (!date) rfc822_date (date = tmp);
+ if (!mail_parse_date (&elt,date)) {
+ sprintf (tmp,"Bad date in append: %.80s",date);
+ MM_LOG (tmp,ERROR);
+ }
+ else { /* user wants to suppress time zones? */
+ if (mail_parameters (NIL,GET_NOTIMEZONES,NIL)) {
+ time_t when = mail_longdate (&elt);
+ date = ctime (&when); /* use traditional date */
+ }
+ /* use POSIX-style date */
+ else date = mail_cdate (tmp,&elt);
+ if (!SIZE (message)) MM_LOG ("Append of zero-length message",ERROR);
+ else if (!unix_collect_msg (tstream,sf,flags,date,message)) {
+ sprintf (tmp,"Error writing scratch file: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ }
+ /* get next message */
+ else if (MM_APPEND (af) (tstream,data,&flags,&date,&message)) continue;
+ }
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ } while (message); /* until no more messages */
+ if (fflush (sf)) {
+ sprintf (tmp,"Error finishing scratch file: %.80s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ fclose (sf); /* punt scratch file */
+ return NIL; /* give up */
+ }
+ i = ftell (sf); /* size of scratch file */
+ /* close sniffing stream */
+ if (tstream != stream) tstream = mail_close (tstream);
+
+ MM_CRITICAL (stream); /* go critical */
+ /* try to open readwrite for UIDPLUS */
+ if ((tstream = mail_open_work (&unixdriver,NIL,mailbox,
+ OP_SILENT|OP_NOKOD)) && tstream->rdonly)
+ tstream = mail_close (tstream);
+ if (au && !tstream) { /* wanted an APPENDUID? */
+ sprintf (tmp,"Unable to re-open mailbox for APPENDUID: %.80s",mailbox);
+ MM_LOG (tmp,WARN);
+ au = NIL;
+ }
+ if (((fd = unix_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lock,LOCK_EX)) < 0) ||
+ !(df = fdopen (fd,"ab"))) {
+ MM_NOCRITICAL (stream); /* done with critical */
+ sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ return NIL;
+ }
+ fstat (fd,&sbuf); /* get current file size */
+ rewind (sf);
+ tp[1] = time (0); /* set mtime to now */
+ /* write all messages */
+ if (!unix_append_msgs (tstream,sf,df,au ? dst : NIL) ||
+ (fflush (df) == EOF) || fsync (fd)) {
+ sprintf (buf,"Message append failed: %s",strerror (errno));
+ MM_LOG (buf,ERROR);
+ ftruncate (fd,sbuf.st_size);
+ tp[0] = /* preserve \Marked status */
+ ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
+ sbuf.st_atime : tp[1];
+ ret = NIL; /* return error */
+ }
+ else tp[0] = tp[1] - 1; /* set atime to now-1 if successful copy */
+ utime (file,tp); /* set the times */
+ fclose (sf); /* done with scratch file */
+ /* force UIDVALIDITY assignment now */
+ if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0);
+ /* return sets if doing APPENDUID */
+ if (au && ret) (*au) (mailbox,tstream->uid_validity,dst);
+ else mail_free_searchset (&dst);
+ unix_unlock (fd,NIL,&lock); /* unlock and close mailbox */
+ fclose (df); /* note that unix_unlock() released the fd */
+ if (tstream) { /* update last UID if we can */
+ UNIXLOCAL *local = (UNIXLOCAL *) tstream->local;
+ local->dirty = T; /* do a rewrite */
+ local->appending = T; /* but not at the cost of marking as old */
+ tstream = mail_close (tstream);
+ }
+ MM_NOCRITICAL (stream); /* release critical */
+ return ret;
+}
+
+/* Collect and write single message to append scratch file
+ * Accepts: MAIL stream
+ * scratch file
+ * flags
+ * date
+ * message stringstruct
+ * Returns: NIL if write error, else T
+ */
+
+int unix_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
+ STRING *msg)
+{
+ unsigned char *s,*t;
+ unsigned long uf;
+ long f = mail_parse_flags (stream,flags,&uf);
+ /* write metadata, note date ends with NL */
+ if (fprintf (sf,"%ld %lu %s",f,SIZE (msg) + 1,date) < 0) return NIL;
+ while (uf) /* write user flags */
+ if ((s = stream->user_flags[find_rightmost_bit (&uf)]) &&
+ (fprintf (sf," %s",s) < 0)) return NIL;
+ if (putc ('\n',sf) == EOF) return NIL;
+ while (SIZE (msg)) { /* copy text to scratch file */
+ for (s = (unsigned char *) msg->curpos, t = s + msg->cursize; s < t; ++s)
+ if (!*s) *s = 0x80; /* disallow NUL */
+ /* write buffered text */
+ if (fwrite (msg->curpos,1,msg->cursize,sf) == msg->cursize)
+ SETPOS (msg,GETPOS (msg) + msg->cursize);
+ else return NIL; /* failed */
+ }
+ /* write trailing newline and return */
+ return (putc ('\n',sf) == EOF) ? NIL : T;
+}
+
+/* Append messages from scratch file to mailbox
+ * Accepts: MAIL stream
+ * source file
+ * destination file
+ * uidset to update if non-NIL
+ * Returns: T if success, NIL if failure
+ */
+
+int unix_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set)
+{
+ int ti,zn,c;
+ long f;
+ unsigned long i,j;
+ char *x,tmp[MAILTMPLEN];
+ int hdrp = T;
+ /* get message metadata line */
+ while (fgets (tmp,MAILTMPLEN,sf)) {
+ if (!(isdigit (tmp[0]) && strchr (tmp,'\n'))) return NIL;
+ f = strtol (tmp,&x,10); /* get flags */
+ if (!((*x++ == ' ') && isdigit (*x))) return NIL;
+ i = strtoul (x,&x,10); /* get message size */
+ if ((*x++ != ' ') || /* build initial header */
+ (fprintf (df,"From %s@%s %sStatus: ",myusername(),mylocalhost(),x)<0)||
+ (f&fSEEN && (putc ('R',df) == EOF)) ||
+ (fputs ("\nX-Status: ",df) == EOF) ||
+ (f&fDELETED && (putc ('D',df) == EOF)) ||
+ (f&fFLAGGED && (putc ('F',df) == EOF)) ||
+ (f&fANSWERED && (putc ('A',df) == EOF)) ||
+ (f&fDRAFT && (putc ('T',df) == EOF)) ||
+ (fputs ("\nX-Keywords:",df) == EOF)) return NIL;
+ /* copy keywords */
+ while ((c = getc (sf)) != '\n') switch (c) {
+ case EOF:
+ return NIL;
+ default:
+ if (putc (c,df) == EOF) return NIL;
+ }
+ if ((putc ('\n',df) == EOF) ||
+ (set && (fprintf (df,"X-UID: %lu\n",++(stream->uid_last)) < 0)))
+ return NIL;
+
+ for (c = '\n'; i && fgets (tmp,MAILTMPLEN,sf); c = tmp[j-1]) {
+ /* get read line length */
+ if (i < (j = strlen (tmp))) fatal ("unix_append_msgs overrun");
+ i -= j; /* number of bytes left */
+ /* squish out CRs (note also copies NUL) */
+ for (x = tmp; x = strchr (x,'\r'); --j) memmove (x,x+1,j-(x-tmp));
+ if (!j) continue; /* do nothing if line emptied */
+ /* start of line? */
+ if ((c == '\n')) switch (tmp[0]) {
+ case 'F': /* possible "From " (case counts here) */
+ if ((j > 4) && (tmp[0] == 'F') && (tmp[1] == 'r') && (tmp[2] == 'o') &&
+ (tmp[3] == 'm') && (tmp[4] == ' ')) {
+ if (!unix_fromwidget) {
+ VALID (tmp,x,ti,zn);/* conditional, only write widget if */
+ if (!ti) break; /* it looks like a valid header */
+ } /* write the widget */
+ if (putc ('>',df) == EOF) return NIL;
+ }
+ break;
+ case 'S': case 's': /* possible "Status:" */
+ if (hdrp && (j > 6) && ((tmp[1] == 't') || (tmp[1] == 'T')) &&
+ ((tmp[2] == 'a') || (tmp[2] == 'A')) &&
+ ((tmp[3] == 't') || (tmp[3] == 'T')) &&
+ ((tmp[4] == 'u') || (tmp[4] == 'U')) &&
+ ((tmp[5] == 's') || (tmp[5] == 'S')) && (tmp[6] == ':') &&
+ (fputs ("X-Original-",df) == EOF)) return NIL;
+ break;
+ case 'X': case 'x': /* possible X-??? header */
+ if (hdrp && (tmp[1] == '-') &&
+ /* possible X-UID: */
+ (((j > 5) && ((tmp[2] == 'U') || (tmp[2] == 'u')) &&
+ ((tmp[3] == 'I') || (tmp[3] == 'i')) &&
+ ((tmp[4] == 'D') || (tmp[4] == 'd')) && (tmp[5] == ':')) ||
+ /* possible X-IMAP: */
+ ((j > 6) && ((tmp[2] == 'I') || (tmp[2] == 'i')) &&
+ ((tmp[3] == 'M') || (tmp[3] == 'm')) &&
+ ((tmp[4] == 'A') || (tmp[4] == 'a')) &&
+ ((tmp[5] == 'P') || (tmp[5] == 'p')) &&
+ ((tmp[6] == ':') ||
+ /* or X-IMAPbase: */
+ ((j > 10) && ((tmp[6] == 'b') || (tmp[6] == 'B')) &&
+ ((tmp[7] == 'a') || (tmp[7] == 'A')) &&
+ ((tmp[8] == 's') || (tmp[8] == 'S')) &&
+ ((tmp[9] == 'e') || (tmp[9] == 'E')) && (tmp[10] == ':')))) ||
+ /* possible X-Status: */
+ ((j > 8) && ((tmp[2] == 'S') || (tmp[2] == 's')) &&
+ ((tmp[3] == 't') || (tmp[3] == 'T')) &&
+ ((tmp[4] == 'a') || (tmp[4] == 'A')) &&
+ ((tmp[5] == 't') || (tmp[5] == 'T')) &&
+ ((tmp[6] == 'u') || (tmp[6] == 'U')) &&
+ ((tmp[7] == 's') || (tmp[7] == 'S')) && (tmp[8] == ':')) ||
+ /* possible X-Keywords: */
+ ((j > 10) && ((tmp[2] == 'K') || (tmp[2] == 'k')) &&
+ ((tmp[3] == 'e') || (tmp[3] == 'E')) &&
+ ((tmp[4] == 'y') || (tmp[4] == 'Y')) &&
+ ((tmp[5] == 'w') || (tmp[5] == 'W')) &&
+ ((tmp[6] == 'o') || (tmp[6] == 'O')) &&
+ ((tmp[7] == 'r') || (tmp[7] == 'R')) &&
+ ((tmp[8] == 'd') || (tmp[8] == 'D')) &&
+ ((tmp[9] == 's') || (tmp[9] == 'S')) && (tmp[10] == ':'))) &&
+ (fputs ("X-Original-",df) == EOF)) return NIL;
+ case '\n': /* blank line */
+ hdrp = NIL;
+ break;
+ default: /* nothing to do */
+ break;
+ }
+ /* just write the line */
+ if (fwrite (tmp,1,j,df) != j) return NIL;
+ }
+ if (i) return NIL; /* didn't read entire message */
+ /* update set */
+ if (stream) mail_append_set (set,stream->uid_last);
+ }
+ return T;
+}
+
+/* Internal routines */
+
+
+/* UNIX mail abort stream
+ * Accepts: MAIL stream
+ */
+
+void unix_abort (MAILSTREAM *stream)
+{
+ if (LOCAL) { /* only if a file is open */
+ if (LOCAL->fd >= 0) close (LOCAL->fd);
+ if (LOCAL->ld >= 0) { /* have a mailbox lock? */
+ flock (LOCAL->ld,LOCK_UN);/* yes, release the lock */
+ close (LOCAL->ld); /* close the lock file */
+ unlink (LOCAL->lname); /* and delete it */
+ }
+ if (LOCAL->lname) fs_give ((void **) &LOCAL->lname);
+ /* free local text buffers */
+ if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
+ if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data);
+ if (LOCAL->linebuf) fs_give ((void **) &LOCAL->linebuf);
+ if (LOCAL->line) fs_give ((void **) &LOCAL->line);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+/* UNIX open and lock mailbox
+ * Accepts: file name to open/lock
+ * file open mode
+ * destination buffer for lock file name
+ * type of locking operation (LOCK_SH or LOCK_EX)
+ */
+
+int unix_lock (char *file,int flags,int mode,DOTLOCK *lock,int op)
+{
+ int fd;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ (*bn) (BLOCK_FILELOCK,NIL);
+ /* try locking the easy way */
+ if (dotlock_lock (file,lock,-1)) {
+ /* got dotlock file, easy open */
+ if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
+ else dotlock_unlock (lock); /* open failed, free the dotlock */
+ }
+ /* no dot lock file, open file now */
+ else if ((fd = open (file,flags,mode)) >= 0) {
+ /* try paranoid way to make a dot lock file */
+ if (dotlock_lock (file,lock,fd)) {
+ close (fd); /* get fresh fd in case of timing race */
+ if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
+ /* open failed, free the dotlock */
+ else dotlock_unlock (lock);
+ }
+ else flock (fd,op); /* paranoid way failed, just flock() it */
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return fd;
+}
+
+/* UNIX unlock and close mailbox
+ * Accepts: file descriptor
+ * (optional) mailbox stream to check atime/mtime
+ * (optional) lock file name
+ */
+
+void unix_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock)
+{
+ if (stream) { /* need to muck with times? */
+ struct stat sbuf;
+ time_t tp[2];
+ time_t now = time (0);
+ fstat (fd,&sbuf); /* get file times */
+ if (LOCAL->ld >= 0) { /* yes, readwrite session? */
+ tp[0] = now; /* set atime to now */
+ /* set mtime to (now - 1) if necessary */
+ tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
+ }
+ else if (stream->recent) { /* readonly with recent messages */
+ if ((sbuf.st_atime >= sbuf.st_mtime) ||
+ (sbuf.st_atime >= sbuf.st_ctime))
+ /* keep past mtime, whack back atime */
+ tp[0] = (tp[1] = (sbuf.st_mtime < now) ? sbuf.st_mtime : now) - 1;
+ else now = 0; /* no time change needed */
+ }
+ /* readonly with no recent messages */
+ else if ((sbuf.st_atime < sbuf.st_mtime) ||
+ (sbuf.st_atime < sbuf.st_ctime)) {
+ tp[0] = now; /* set atime to now */
+ /* set mtime to (now - 1) if necessary */
+ tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
+ }
+ else now = 0; /* no time change needed */
+ /* set the times, note change */
+ if (now && !utime (stream->mailbox,tp)) LOCAL->filetime = tp[1];
+ }
+ flock (fd,LOCK_UN); /* release flock'ers */
+ if (!stream) close (fd); /* close the file if no stream */
+ dotlock_unlock (lock); /* flush the lock file if any */
+}
+
+/* UNIX mail parse and lock mailbox
+ * Accepts: MAIL stream
+ * space to write lock file name
+ * type of locking operation
+ * Returns: T if parse OK, critical & mailbox is locked shared; NIL if failure
+ */
+
+int unix_parse (MAILSTREAM *stream,DOTLOCK *lock,int op)
+{
+ int zn;
+ unsigned long i,j,k,m;
+ unsigned char c,*s,*t,*u,tmp[MAILTMPLEN],date[30];
+ int ti = 0,retain = T;
+ unsigned long nmsgs = stream->nmsgs;
+ unsigned long prevuid = nmsgs ? mail_elt (stream,nmsgs)->private.uid : 0;
+ unsigned long recent = stream->recent;
+ unsigned long oldnmsgs = stream->nmsgs;
+ short silent = stream->silent;
+ short pseudoseen = NIL;
+ struct stat sbuf;
+ STRING bs;
+ FDDATA d;
+ MESSAGECACHE *elt;
+ mail_lock (stream); /* guard against recursion or pingers */
+ /* toss out previous descriptor */
+ if (LOCAL->fd >= 0) close (LOCAL->fd);
+ MM_CRITICAL (stream); /* open and lock mailbox (shared OK) */
+ if ((LOCAL->fd = unix_lock (stream->mailbox,(LOCAL->ld >= 0) ?
+ O_RDWR : O_RDONLY,
+ (long)mail_parameters(NIL,GET_MBXPROTECTION,NIL),
+ lock,op)) < 0) {
+ sprintf (tmp,"Mailbox open failed, aborted: %s",strerror (errno));
+ MM_LOG (tmp,ERROR);
+ unix_abort (stream);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream); /* done with critical */
+ return NIL;
+ }
+ fstat (LOCAL->fd,&sbuf); /* get status */
+ /* validate change in size */
+ if (sbuf.st_size < LOCAL->filesize) {
+ sprintf (tmp,"Mailbox shrank from %lu to %lu bytes, aborted",
+ (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
+ MM_LOG (tmp,ERROR); /* this is pretty bad */
+ unix_unlock (LOCAL->fd,stream,lock);
+ unix_abort (stream);
+ mail_unlock (stream);
+ MM_NOCRITICAL (stream); /* done with critical */
+ return NIL;
+ }
+
+ /* new data? */
+ else if (i = sbuf.st_size - LOCAL->filesize) {
+ d.fd = LOCAL->fd; /* yes, set up file descriptor */
+ d.pos = LOCAL->filesize; /* get to that position in the file */
+ d.chunk = LOCAL->buf; /* initial buffer chunk */
+ d.chunksize = CHUNKSIZE; /* file chunk size */
+ INIT (&bs,fd_string,&d,i); /* initialize stringstruct */
+ /* skip leading whitespace for broken MTAs */
+ while (((c = CHR (&bs)) == '\n') || (c == '\r') ||
+ (c == ' ') || (c == '\t')) SNX (&bs);
+ if (SIZE (&bs)) { /* read new data */
+ /* remember internal header position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ s = unix_mbxline (stream,&bs,&i);
+ t = NIL,zn = 0;
+ if (i) VALID (s,t,ti,zn); /* see if valid From line */
+ if (!ti) { /* someone pulled the rug from under us */
+ sprintf (tmp,"Unexpected changes to mailbox (try restarting): %.20s",
+ (char *) s);
+ MM_LOG (tmp,ERROR);
+ unix_unlock (LOCAL->fd,stream,lock);
+ unix_abort (stream);
+ mail_unlock (stream);
+ /* done with critical */
+ MM_NOCRITICAL (stream);
+ return NIL;
+ }
+ stream->silent = T; /* quell main program new message events */
+ do { /* found a message */
+ /* instantiate first new message */
+ mail_exists (stream,++nmsgs);
+ (elt = mail_elt (stream,nmsgs))->valid = T;
+ recent++; /* assume recent by default */
+ elt->recent = T;
+ /* note position/size of internal header */
+ elt->private.special.offset = j;
+ elt->private.msg.header.offset = elt->private.special.text.size = i;
+
+ /* generate plausible IMAPish date string */
+ date[2] = date[6] = date[20] = '-'; date[11] = ' ';
+ date[14] = date[17] = ':';
+ /* dd */
+ date[0] = t[ti - 2]; date[1] = t[ti - 1];
+ /* mmm */
+ date[3] = t[ti - 6]; date[4] = t[ti - 5]; date[5] = t[ti - 4];
+ /* hh */
+ date[12] = t[ti + 1]; date[13] = t[ti + 2];
+ /* mm */
+ date[15] = t[ti + 4]; date[16] = t[ti + 5];
+ if (t[ti += 6] == ':') {/* ss */
+ date[18] = t[++ti]; date[19] = t[++ti];
+ ti++; /* move to space */
+ }
+ else date[18] = date[19] = '0';
+ /* yy -- advance over timezone if necessary */
+ if (zn == ti) ti += (((t[zn+1] == '+') || (t[zn+1] == '-')) ? 6 : 4);
+ date[7] = t[ti + 1]; date[8] = t[ti + 2];
+ date[9] = t[ti + 3]; date[10] = t[ti + 4];
+ /* zzz */
+ t = zn ? (t + zn + 1) : (unsigned char *) "LCL";
+ date[21] = *t++; date[22] = *t++; date[23] = *t++;
+ if ((date[21] != '+') && (date[21] != '-')) date[24] = '\0';
+ else { /* numeric time zone */
+ date[24] = *t++; date[25] = *t++;
+ date[26] = '\0'; date[20] = ' ';
+ }
+ /* set internal date */
+ if (!mail_parse_date (elt,date)) {
+ sprintf (tmp,"Unable to parse internal date: %s",(char *) date);
+ MM_LOG (tmp,WARN);
+ }
+
+ do { /* look for message body */
+ s = t = unix_mbxline (stream,&bs,&i);
+ if (i) switch (*s) { /* check header lines */
+ case 'X': /* possible X-???: line */
+ if (s[1] == '-') { /* must be immediately followed by hyphen */
+ /* X-Status: becomes Status: in S case */
+ if (s[2] == 'S' && s[3] == 't' && s[4] == 'a' && s[5] == 't' &&
+ s[6] == 'u' && s[7] == 's' && s[8] == ':') s += 2;
+ /* possible X-Keywords */
+ else if (s[2] == 'K' && s[3] == 'e' && s[4] == 'y' &&
+ s[5] == 'w' && s[6] == 'o' && s[7] == 'r' &&
+ s[8] == 'd' && s[9] == 's' && s[10] == ':') {
+ SIZEDTEXT uf;
+ retain = NIL; /* don't retain continuation */
+ s += 11; /* flush leading whitespace */
+ while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n'))){
+ while (*s == ' ') s++;
+ /* find end of keyword */
+ if (!(u = strpbrk (s," \n\r"))) u = s + strlen (s);
+ /* got a keyword? */
+ if ((k = (u - s)) && (k <= MAXUSERFLAG)) {
+ uf.data = (unsigned char *) s;
+ uf.size = k;
+ for (j = 0; (j < NUSERFLAGS) && stream->user_flags[j]; ++j)
+ if (!compare_csizedtext (stream->user_flags[j],&uf)) {
+ elt->user_flags |= ((long) 1) << j;
+ break;
+ }
+ }
+ s = u; /* advance to next keyword */
+ }
+ break;
+ }
+
+ /* possible X-IMAP */
+ else if ((s[2] == 'I') && (s[3] == 'M') && (s[4] == 'A') &&
+ (s[5] == 'P') && ((m = (s[6] == ':')) ||
+ ((s[6] == 'b') && (s[7] == 'a') &&
+ (s[8] == 's') && (s[9] == 'e') &&
+ (s[10] == ':')))) {
+ retain = NIL; /* don't retain continuation */
+ if ((nmsgs == 1) && !stream->uid_validity) {
+ /* advance to data */
+ s += m ? 7 : 11;
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ j = 0; /* slurp UID validity */
+ /* found a digit? */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ /* must have valid UID validity and UID last */
+ if (j && isdigit (*s)) {
+ /* pseudo-header seen if X-IMAP */
+ if (m) pseudoseen = LOCAL->pseudo = T;
+ /* save UID validity */
+ stream->uid_validity = j;
+ j = 0; /* slurp UID last */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* save UID last */
+ stream->uid_last = j;
+ /* process keywords */
+ for (j = 0; (*s != '\n') && ((*s != '\r')||(s[1] != '\n'));
+ s = u,j++) {
+ /* flush leading whitespace */
+ while (*s == ' ') s++;
+ u = strpbrk (s," \n\r");
+ /* got a keyword? */
+ if ((j < NUSERFLAGS) && (k = (u - s)) &&
+ (k <= MAXUSERFLAG)) {
+ if (stream->user_flags[j])
+ fs_give ((void **) &stream->user_flags[j]);
+ stream->user_flags[j] = (char *) fs_get (k + 1);
+ strncpy (stream->user_flags[j],s,k);
+ stream->user_flags[j][k] = '\0';
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ /* possible X-UID */
+ else if (s[2] == 'U' && s[3] == 'I' && s[4] == 'D' &&
+ s[5] == ':') {
+ retain = NIL; /* don't retain continuation */
+ /* only believe if have a UID validity */
+ if (stream->uid_validity && ((nmsgs > 1) || !pseudoseen)) {
+ s += 6; /* advance to UID value */
+ /* flush whitespace */
+ while (*s == ' ') s++;
+ j = 0;
+ /* found a digit? */
+ while (isdigit (*s)) {
+ j *= 10; /* yes, add it in */
+ j += *s++ - '0';
+ }
+ /* flush remainder of line */
+ while (*s != '\n') s++;
+ /* make sure not duplicated */
+ if (elt->private.uid)
+ sprintf (tmp,"Message %lu UID %lu already has UID %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,elt->private.uid);
+ /* make sure UID doesn't go backwards */
+ else if (j <= prevuid)
+ sprintf (tmp,"Message %lu UID %lu less than %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,prevuid + 1);
+#if 0 /* this is currently broken by UIDPLUS */
+ /* or skip by mailbox's recorded last */
+ else if (j > stream->uid_last)
+ sprintf (tmp,"Message %lu UID %lu greater than last %lu",
+ pseudoseen ? elt->msgno - 1 : elt->msgno,
+ j,stream->uid_last);
+#endif
+ else { /* normal UID case */
+ prevuid = elt->private.uid = j;
+#if 1 /* temporary kludge for UIDPLUS */
+ if (prevuid > stream->uid_last) {
+ stream->uid_last = prevuid;
+ LOCAL->ddirty = LOCAL->dirty = T;
+ }
+#endif
+ break; /* exit this cruft */
+ }
+ MM_LOG (tmp,WARN);
+ /* invalidate UID validity */
+ stream->uid_validity = 0;
+ elt->private.uid = 0;
+ }
+ break;
+ }
+ }
+ /* otherwise fall into S case */
+
+ case 'S': /* possible Status: line */
+ if (s[0] == 'S' && s[1] == 't' && s[2] == 'a' && s[3] == 't' &&
+ s[4] == 'u' && s[5] == 's' && s[6] == ':') {
+ retain = NIL; /* don't retain continuation */
+ s += 6; /* advance to status flags */
+ do switch (*s++) {/* parse flags */
+ case 'R': /* message read */
+ elt->seen = T;
+ break;
+ case 'O': /* message old */
+ if (elt->recent) {
+ elt->recent = NIL;
+ recent--; /* it really wasn't recent */
+ }
+ break;
+ case 'D': /* message deleted */
+ elt->deleted = T;
+ break;
+ case 'F': /* message flagged */
+ elt->flagged = T;
+ break;
+ case 'A': /* message answered */
+ elt->answered = T;
+ break;
+ case 'T': /* message is a draft */
+ elt->draft = T;
+ break;
+ default: /* some other crap */
+ break;
+ } while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n')));
+ break; /* all done */
+ }
+ /* otherwise fall into default case */
+
+ default: /* ordinary header line */
+ if ((*s == 'S') || (*s == 's') ||
+ (((*s == 'X') || (*s == 'x')) && (s[1] == '-'))) {
+ unsigned char *e,*v;
+ /* must match what mail_filter() does */
+ for (u = s,v = tmp,e = u + min (i,MAILTMPLEN - 1);
+ (u < e) && ((c = (*u ? *u : (*u = ' '))) != ':') &&
+ ((c > ' ') || ((c != ' ') && (c != '\t') &&
+ (c != '\r') && (c != '\n')));
+ *v++ = *u++);
+ *v = '\0'; /* tie off */
+ /* matches internal header? */
+ if (!compare_cstring (tmp,"STATUS") ||
+ !compare_cstring (tmp,"X-STATUS") ||
+ !compare_cstring (tmp,"X-KEYWORDS") ||
+ !compare_cstring (tmp,"X-UID") ||
+ !compare_cstring (tmp,"X-IMAP") ||
+ !compare_cstring (tmp,"X-IMAPBASE")) {
+ char err[MAILTMPLEN];
+ sprintf (err,"Discarding bogus %s header in message %lu",
+ (char *) tmp,elt->msgno);
+ MM_LOG (err,WARN);
+ retain = NIL; /* don't retain continuation */
+ break; /* different case or something */
+ }
+ }
+ /* retain or non-continuation? */
+ if (retain || ((*s != ' ') && (*s != '\t'))) {
+ retain = T; /* retaining continuation now */
+ /* line length in LF format newline */
+ for (j = k = 0; j < i; ++j) if (s[j] != '\r') ++k;
+ /* "internal" header size */
+ elt->private.spare.data += k;
+ /* message size */
+ elt->rfc822_size += k + 1;
+ }
+ else {
+ char err[MAILTMPLEN];
+ sprintf (err,"Discarding bogus continuation in msg %lu: %.80s",
+ elt->msgno,(char *) s);
+ if (u = strpbrk (err,"\r\n")) *u = '\0';
+ MM_LOG (err,WARN);
+ break; /* different case or something */
+ }
+ break;
+ }
+ } while (i && (*t != '\n') && ((*t != '\r') || (t[1] != '\n')));
+ /* "internal" header sans trailing newline */
+ if (i) elt->private.spare.data--;
+ /* assign a UID if none found */
+ if (((nmsgs > 1) || !pseudoseen) && !elt->private.uid) {
+ prevuid = elt->private.uid = ++stream->uid_last;
+ elt->private.dirty = T;
+ LOCAL->ddirty = T; /* force update */
+ }
+ else elt->private.dirty = elt->recent;
+
+ /* note size of header, location of text */
+ elt->private.msg.header.text.size =
+ (elt->private.msg.text.offset =
+ (LOCAL->filesize + GETPOS (&bs)) - elt->private.special.offset) -
+ elt->private.special.text.size;
+ k = m = 0; /* no previous line size yet */
+ /* note current position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ if (i) do { /* look for next message */
+ s = unix_mbxline (stream,&bs,&i);
+ if (i) { /* got new data? */
+ VALID (s,t,ti,zn); /* yes, parse line */
+ if (!ti) { /* not a header line, add it to message */
+ elt->rfc822_size += i;
+ for (j = 0; j < i; ++j) switch (s[j]) {
+ case '\r': /* squeeze out CRs */
+ elt->rfc822_size -= 1;
+ break;
+ case '\n': /* LF becomes CRLF */
+ elt->rfc822_size += 1;
+ break;
+ default:
+ break;
+ }
+ if ((i == 1) && (*s == '\n')) {
+ k = 2;
+ m = 1;
+ }
+ else if ((i == 2) && (*s == '\r') && (s[1] == '\n'))
+ k = m = 2;
+ else k = m = 0; /* file does not end with newline! */
+ /* update current position */
+ j = LOCAL->filesize + GETPOS (&bs);
+ }
+ }
+ } while (i && !ti); /* until found a header */
+ elt->private.msg.text.text.size = j -
+ (elt->private.special.offset + elt->private.msg.text.offset);
+ /* flush ending blank line */
+ elt->private.msg.text.text.size -= m;
+ elt->rfc822_size -= k;
+ /* until end of buffer */
+ } while (!stream->sniff && i);
+ if (pseudoseen) { /* flush pseudo-message if present */
+ /* decrement recent count */
+ if (mail_elt (stream,1)->recent) recent--;
+ /* and the exists count */
+ mail_exists (stream,nmsgs--);
+ mail_expunged(stream,1);/* fake an expunge of that message */
+ }
+ /* need to start a new UID validity? */
+ if (!stream->uid_validity) {
+ stream->uid_validity = time (0);
+ /* in case a whiner with no life */
+ if (mail_parameters (NIL,GET_USERHASNOLIFE,NIL))
+ stream->uid_nosticky = T;
+ else if (nmsgs) { /* don't bother if empty file */
+ /* make dirty to restart UID epoch */
+ LOCAL->ddirty = LOCAL->dirty = T;
+ /* need to rewrite msg 1 if not pseudo */
+ if (!LOCAL->pseudo) mail_elt (stream,1)->private.dirty = T;
+ MM_LOG ("Assigning new unique identifiers to all messages",NIL);
+ }
+ }
+ stream->nmsgs = oldnmsgs; /* whack it back down */
+ stream->silent = silent; /* restore old silent setting */
+ /* notify upper level of new mailbox sizes */
+ mail_exists (stream,nmsgs);
+ mail_recent (stream,recent);
+ /* mark dirty so O flags are set */
+ if (recent) LOCAL->dirty = T;
+ }
+ }
+ /* no change, don't babble if never got time */
+ else if (LOCAL->filetime && LOCAL->filetime != sbuf.st_mtime)
+ MM_LOG ("New mailbox modification time but apparently no changes",WARN);
+ /* update parsed file size and time */
+ LOCAL->filesize = sbuf.st_size;
+ LOCAL->filetime = sbuf.st_mtime;
+ return T; /* return the winnage */
+}
+
+/* UNIX read line from mailbox
+ * Accepts: mail stream
+ * stringstruct
+ * pointer to line size
+ * Returns: pointer to input line
+ */
+
+char *unix_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size)
+{
+ unsigned long i,j,k,m;
+ char *s,*t,*te;
+ char *ret = "";
+ /* flush old buffer */
+ if (LOCAL->line) fs_give ((void **) &LOCAL->line);
+ /* if buffer needs refreshing */
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ if (SIZE (bs)) { /* find newline */
+ /* end of fast scan */
+ te = (t = (s = bs->curpos) + bs->cursize) - 12;
+ while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
+ --s; /* back up */
+ break; /* exit loop */
+ }
+ /* final character-at-a-time scan */
+ while ((s < t) && (*s != '\n')) ++s;
+ /* difficult case if line spans buffer */
+ if ((i = s - bs->curpos) == bs->cursize) {
+ /* have space in line buffer? */
+ if (i > LOCAL->linebuflen) {
+ fs_give ((void **) &LOCAL->linebuf);
+ LOCAL->linebuf = (char *) fs_get (LOCAL->linebuflen = i);
+ }
+ /* remember what we have so far */
+ memcpy (LOCAL->linebuf,bs->curpos,i);
+ /* load next buffer */
+ SETPOS (bs,k = GETPOS (bs) + i);
+ /* end of fast scan */
+ te = (t = (s = bs->curpos) + bs->cursize) - 12;
+ /* fast scan in overlap buffer */
+ while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
+ (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
+ --s; /* back up */
+ break; /* exit loop */
+ }
+
+ /* final character-at-a-time scan */
+ while ((s < t) && (*s != '\n')) ++s;
+ /* huge line? */
+ if ((j = s - bs->curpos) == bs->cursize) {
+ SETPOS (bs,GETPOS (bs) + j);
+ /* look for end of line (s-l-o-w!!) */
+ for (m = SIZE (bs); m && (SNX (bs) != '\n'); --m,++j);
+ SETPOS (bs,k); /* go back to where it started */
+ }
+ /* got size of data, make buffer for return */
+ ret = LOCAL->line = (char *) fs_get (i + j + 2);
+ /* copy first chunk */
+ memcpy (ret,LOCAL->linebuf,i);
+ while (j) { /* copy remainder */
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ memcpy (ret + i,bs->curpos,k = min (j,bs->cursize));
+ i += k; /* account for this much read in */
+ j -= k;
+ bs->curpos += k; /* increment new position */
+ bs->cursize -= k; /* eat that many bytes */
+ }
+ if (!bs->cursize) SETPOS (bs,GETPOS (bs));
+ /* read newline at end */
+ if (SIZE (bs)) ret[i++] = SNX (bs);
+ ret[i] = '\0'; /* makes debugging easier */
+ }
+ else { /* this is easy */
+ ret = bs->curpos; /* string it at this position */
+ bs->curpos += ++i; /* increment new position */
+ bs->cursize -= i; /* eat that many bytes */
+ }
+ *size = i; /* return that to user */
+ }
+ else *size = 0; /* end of data, return empty */
+ return ret;
+}
+
+/* UNIX make pseudo-header
+ * Accepts: MAIL stream
+ * buffer to write pseudo-header
+ * Returns: length of pseudo-header
+ */
+
+unsigned long unix_pseudo (MAILSTREAM *stream,char *hdr)
+{
+ int i;
+ char *s,tmp[MAILTMPLEN];
+ time_t now = time (0);
+ rfc822_fixed_date (tmp);
+ sprintf (hdr,"From %s %.24s\nDate: %s\nFrom: %s <%s@%.80s>\nSubject: %s\nMessage-ID: <%lu@%.80s>\nX-IMAP: %010lu %010lu",
+ pseudo_from,ctime (&now),
+ tmp,pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
+ (unsigned long) now,mylocalhost (),stream->uid_validity,
+ stream->uid_last);
+ for (s = hdr + strlen (hdr),i = 0; i < NUSERFLAGS; ++i)
+ if (stream->user_flags[i])
+ sprintf (s += strlen (s)," %s",stream->user_flags[i]);
+ sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n\n",pseudo_msg);
+ return strlen (hdr); /* return header length */
+}
+
+/* UNIX make status string
+ * Accepts: MAIL stream
+ * destination string to write
+ * message cache entry
+ * UID to write if non-zero (else use elt->private.uid)
+ * non-zero flag to write UID (.LT. 0 to write UID base info too)
+ * Returns: length of string
+ */
+
+unsigned long unix_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
+ unsigned long uid,long flag)
+{
+ char *t,stack[64];
+ char *s = status;
+ unsigned long n;
+ int pad = 50;
+ int sticky = uid ? T : !stream->uid_nosticky;
+ /* This used to use sprintf(), but thanks to certain cretinous C libraries
+ with horribly slow implementations of sprintf() I had to change it to this
+ mess. At least it should be fast. */
+ if ((flag < 0) && sticky) { /* need to write X-IMAPbase: header? */
+ *s++ = 'X'; *s++ = '-'; *s++ = 'I'; *s++ = 'M'; *s++ = 'A'; *s++ = 'P';
+ *s++ = 'b'; *s++ = 'a'; *s++ = 's'; *s++ = 'e'; *s++ = ':'; *s++ = ' ';
+ t = stack;
+ n = stream->uid_validity; /* push UID validity digits on the stack */
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ /* pop UID validity digits from stack */
+ while (t > stack) *s++ = *--t;
+ *s++ = ' ';
+ n = stream->uid_last; /* push UID last digits on the stack */
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ /* pop UID last digits from stack */
+ while (t > stack) *s++ = *--t;
+ for (n = 0; n < NUSERFLAGS; ++n) if (t = stream->user_flags[n])
+ for (*s++ = ' '; *t; *s++ = *t++);
+ *s++ = '\n';
+ pad += 30; /* increased padding if have IMAPbase */
+ }
+ *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't'; *s++ = 'u'; *s++ = 's';
+ *s++ = ':'; *s++ = ' ';
+ if (elt->seen) *s++ = 'R';
+ /* only write O if have a UID */
+ if (flag && (!elt->recent || !LOCAL->appending)) *s++ = 'O';
+ *s++ = '\n';
+ *s++ = 'X'; *s++ = '-'; *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't';
+ *s++ = 'u'; *s++ = 's'; *s++ = ':'; *s++ = ' ';
+ if (elt->deleted) *s++ = 'D';
+ if (elt->flagged) *s++ = 'F';
+ if (elt->answered) *s++ = 'A';
+ if (elt->draft) *s++ = 'T';
+ *s++ = '\n';
+
+ if (sticky) { /* only do this if UIDs sticky */
+ *s++ = 'X'; *s++ = '-'; *s++ = 'K'; *s++ = 'e'; *s++ = 'y'; *s++ = 'w';
+ *s++ = 'o'; *s++ = 'r'; *s++ = 'd'; *s++ = 's'; *s++ = ':';
+ if (n = elt->user_flags) do {
+ *s++ = ' ';
+ for (t = stream->user_flags[find_rightmost_bit (&n)]; *t; *s++ = *t++);
+ } while (n);
+ n = s - status; /* get size of stuff so far */
+ /* pad X-Keywords to make size constant */
+ if (n < pad) for (n = pad - n; n > 0; --n) *s++ = ' ';
+ *s++ = '\n';
+ if (flag) { /* want to include UID? */
+ t = stack;
+ /* push UID digits on the stack */
+ n = uid ? uid : elt->private.uid;
+ do *t++ = (char) (n % 10) + '0';
+ while (n /= 10);
+ *s++ = 'X'; *s++ = '-'; *s++ = 'U'; *s++ = 'I'; *s++ = 'D'; *s++ = ':';
+ *s++ = ' ';
+ /* pop UID from stack */
+ while (t > stack) *s++ = *--t;
+ *s++ = '\n';
+ }
+ }
+ *s++ = '\n'; *s = '\0'; /* end of extended message status */
+ return s - status; /* return size of resulting string */
+}
+
+/* Rewrite mailbox file
+ * Accepts: MAIL stream, must be critical and locked
+ * return pointer to number of expunged messages if want expunge
+ * lock file name
+ * expunge sequence, not deleted flag
+ * Returns: T if success and mailbox unlocked, NIL if failure
+ */
+
+#define OVERFLOWBUFLEN 8192 /* initial overflow buffer length */
+
+long unix_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock,
+ long flags)
+{
+ MESSAGECACHE *elt;
+ UNIXFILE f;
+ char *s;
+ time_t tp[2];
+ long ret,flag;
+ unsigned long i,j;
+ unsigned long recent = stream->recent;
+ unsigned long size = LOCAL->pseudo ? unix_pseudo (stream,LOCAL->buf) : 0;
+ if (nexp) *nexp = 0; /* initially nothing expunged */
+ /* calculate size of mailbox after rewrite */
+ for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs; i++) {
+ elt = mail_elt (stream,i); /* get cache */
+ if (!(nexp && elt->deleted && (flags ? elt->sequence : T))) {
+ /* add RFC822 size of this message */
+ size += elt->private.special.text.size + elt->private.spare.data +
+ unix_xstatus (stream,LOCAL->buf,elt,NIL,flag) +
+ elt->private.msg.text.text.size + 1;
+ flag = 1; /* only count X-IMAPbase once */
+ }
+ }
+ /* no messages, has a life, and no pseudo */
+ if (!size && !mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) {
+ LOCAL->pseudo = T; /* so make a pseudo-message now */
+ size = unix_pseudo (stream,LOCAL->buf);
+ }
+ /* extend the file as necessary */
+ if (ret = unix_extend (stream,size)) {
+ /* Set up buffered I/O file structure
+ * curpos current position being written through buffering
+ * filepos current position being written physically to the disk
+ * bufpos current position being written in the buffer
+ * protect current maximum position that can be written to the disk
+ * before buffering is forced
+ * The code tries to buffer so that that disk is written in multiples of
+ * OVERBLOWBUFLEN bytes.
+ */
+ f.stream = stream; /* note mail stream */
+ f.curpos = f.filepos = 0; /* start of file */
+ f.protect = stream->nmsgs ? /* initial protection pointer */
+ mail_elt (stream,1)->private.special.offset : 8192;
+ f.bufpos = f.buf = (char *) fs_get (f.buflen = OVERFLOWBUFLEN);
+
+ if (LOCAL->pseudo) /* update pseudo-header */
+ unix_write (&f,LOCAL->buf,unix_pseudo (stream,LOCAL->buf));
+ /* loop through all messages */
+ for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs;) {
+ elt = mail_elt (stream,i);/* get cache */
+ /* expunge this message? */
+ if (nexp && elt->deleted && (flags ? elt->sequence : T)) {
+ /* one less recent message */
+ if (elt->recent) --recent;
+ mail_expunged(stream,i);/* notify upper levels */
+ ++*nexp; /* count up one more expunged message */
+ }
+ else { /* preserve this message */
+ i++; /* advance to next message */
+ if ((flag < 0) || /* need to rewrite message? */
+ elt->private.dirty || (f.curpos != elt->private.special.offset) ||
+ (elt->private.msg.header.text.size !=
+ (elt->private.spare.data +
+ unix_xstatus (stream,LOCAL->buf,elt,NIL,flag)))) {
+ unsigned long newoffset = f.curpos;
+ /* yes, seek to internal header */
+ lseek (LOCAL->fd,elt->private.special.offset,L_SET);
+ read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
+ /* see if need to squeeze out a CR */
+ if (LOCAL->buf[elt->private.special.text.size - 2] == '\r') {
+ LOCAL->buf[--elt->private.special.text.size - 1] = '\n';
+ --size; /* squeezed out a CR from PC */
+ }
+ /* protection pointer moves to RFC822 header */
+ f.protect = elt->private.special.offset +
+ elt->private.msg.header.offset;
+ /* write internal header */
+ unix_write (&f,LOCAL->buf,elt->private.special.text.size);
+ /* get RFC822 header */
+ s = unix_header (stream,elt->msgno,&j,FT_INTERNAL);
+ /* in case this got decremented */
+ elt->private.msg.header.offset = elt->private.special.text.size;
+ /* header size, sans trailing newline */
+ if ((j < 2) || (s[j - 2] == '\n')) j--;
+ /* this can happen if CRs were squeezed */
+ if (j < elt->private.spare.data) {
+ /* so fix up counts */
+ size -= elt->private.spare.data - j;
+ elt->private.spare.data = j;
+ }
+ else if (j != elt->private.spare.data)
+ fatal ("header size inconsistent");
+ /* protection pointer moves to RFC822 text */
+ f.protect = elt->private.special.offset +
+ elt->private.msg.text.offset;
+ unix_write (&f,s,j); /* write RFC822 header */
+ /* write status and UID */
+ unix_write (&f,LOCAL->buf,
+ j = unix_xstatus (stream,LOCAL->buf,elt,NIL,flag));
+ flag = 1; /* only write X-IMAPbase once */
+ /* new file header size */
+ elt->private.msg.header.text.size = elt->private.spare.data + j;
+
+ /* did text move? */
+ if (f.curpos != f.protect) {
+ /* get message text */
+ s = unix_text_work (stream,elt,&j,FT_INTERNAL);
+ /* this can happen if CRs were squeezed */
+ if (j < elt->private.msg.text.text.size) {
+ /* so fix up counts */
+ size -= elt->private.msg.text.text.size - j;
+ elt->private.msg.text.text.size = j;
+ }
+ /* can't happen it says here */
+ else if (j > elt->private.msg.text.text.size)
+ fatal ("text size inconsistent");
+ /* new text offset, status/UID may change it */
+ elt->private.msg.text.offset = f.curpos - newoffset;
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset : (f.curpos + j + 1);
+ unix_write (&f,s,j);/* write text */
+ /* write trailing newline */
+ unix_write (&f,"\n",1);
+ }
+ else { /* tie off header and status */
+ unix_write (&f,NIL,NIL);
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset : size;
+ /* locate end of message text */
+ j = f.filepos + elt->private.msg.text.text.size;
+ /* trailing newline already there? */
+ if (f.protect == (j + 1)) f.curpos = f.filepos = f.protect;
+ else { /* trailing newline missing, write it */
+ f.curpos = f.filepos = j;
+ unix_write (&f,"\n",1);
+ }
+ }
+ /* new internal header offset */
+ elt->private.special.offset = newoffset;
+ elt->private.dirty =NIL;/* message is now clean */
+ }
+ else { /* no need to rewrite this message */
+ /* tie off previous message if needed */
+ unix_write (&f,NIL,NIL);
+ /* protection pointer moves to next message */
+ f.protect = (i <= stream->nmsgs) ?
+ mail_elt (stream,i)->private.special.offset : size;
+ /* locate end of message text */
+ j = f.filepos + elt->private.special.text.size +
+ elt->private.msg.header.text.size +
+ elt->private.msg.text.text.size;
+ /* trailing newline already there? */
+ if (f.protect == (j + 1)) f.curpos = f.filepos = f.protect;
+ else { /* trailing newline missing, write it */
+ f.curpos = f.filepos = j;
+ unix_write (&f,"\n",1);
+ }
+ }
+ }
+ }
+
+ unix_write (&f,NIL,NIL); /* tie off final message */
+ if (size != f.filepos) fatal ("file size inconsistent");
+ fs_give ((void **) &f.buf); /* free buffer */
+ /* make sure tied off */
+ ftruncate (LOCAL->fd,LOCAL->filesize = size);
+ fsync (LOCAL->fd); /* make sure the updates take */
+ if (size && (flag < 0)) fatal ("lost UID base information");
+ /* no longer dirty */
+ LOCAL->ddirty = LOCAL->dirty = NIL;
+ /* notify upper level of new mailbox sizes */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ /* set atime to now, mtime a second earlier */
+ tp[1] = (tp[0] = time (0)) - 1;
+ /* set the times, note change */
+ if (!utime (stream->mailbox,tp)) LOCAL->filetime = tp[1];
+ close (LOCAL->fd); /* close and reopen file */
+ if ((LOCAL->fd = open (stream->mailbox,O_RDWR,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL)))
+ < 0) {
+ sprintf (LOCAL->buf,"Mailbox open failed, aborted: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,ERROR);
+ unix_abort (stream);
+ }
+ dotlock_unlock (lock); /* flush the lock file */
+ }
+ return ret; /* return state from algorithm */
+}
+
+/* Extend UNIX mailbox file
+ * Accepts: MAIL stream
+ * new desired size
+ * Return: T if success, else NIL
+ */
+
+long unix_extend (MAILSTREAM *stream,unsigned long size)
+{
+ unsigned long i = (size > LOCAL->filesize) ? size - LOCAL->filesize : 0;
+ if (i) { /* does the mailbox need to grow? */
+ if (i > LOCAL->buflen) { /* make sure have enough space */
+ /* this user won the lottery all right */
+ fs_give ((void **) &LOCAL->buf);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1);
+ }
+ memset (LOCAL->buf,'\0',i); /* get a block of nulls */
+ while (T) { /* until write successful or punt */
+ lseek (LOCAL->fd,LOCAL->filesize,L_SET);
+ if ((write (LOCAL->fd,LOCAL->buf,i) >= 0) && !fsync (LOCAL->fd)) break;
+ else {
+ long e = errno; /* note error before doing ftruncate */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ if (MM_DISKERROR (stream,e,NIL)) {
+ fsync (LOCAL->fd); /* user chose to punt */
+ sprintf (LOCAL->buf,"Unable to extend mailbox: %s",strerror (e));
+ if (!stream->silent) MM_LOG (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ }
+ }
+ }
+ return LONGT;
+}
+
+/* Write data to buffered file
+ * Accepts: buffered file pointer
+ * file data or NIL to indicate "flush buffer"
+ * date size (ignored for "flush buffer")
+ * Does not return until success
+ */
+
+void unix_write (UNIXFILE *f,char *buf,unsigned long size)
+{
+ unsigned long i,j,k;
+ if (buf) { /* doing buffered write? */
+ i = f->bufpos - f->buf; /* yes, get size of current buffer data */
+ /* yes, have space in current buffer chunk? */
+ if (j = i ? ((f->buflen - i) % OVERFLOWBUFLEN) : f->buflen) {
+ /* yes, fill up buffer as much as we can */
+ memcpy (f->bufpos,buf,k = min (j,size));
+ f->bufpos += k; /* new buffer position */
+ f->curpos += k; /* new current position */
+ if (j -= k) return; /* all done if still have buffer free space */
+ buf += k; /* full, get new unwritten data pointer */
+ size -= k; /* new data size */
+ i += k; /* new buffer data size */
+ }
+ /* This chunk of the buffer is full. See if can make some space by
+ * writing to the disk, if there's enough unprotected space to do so.
+ * Try to fill out any unaligned chunk, along with any subsequent full
+ * chunks that will fit in unprotected space.
+ */
+ /* any unprotected space we can write to? */
+ if (j = min (i,f->protect - f->filepos)) {
+ /* yes, filepos not at chunk boundary? */
+ if ((k = f->filepos % OVERFLOWBUFLEN) && ((k = OVERFLOWBUFLEN - k) < j))
+ j -= k; /* yes, and can write out partial chunk */
+ else k = 0; /* no partial chunk to write */
+ /* if at least a chunk free, write that too */
+ if (j > OVERFLOWBUFLEN) k += j - (j % OVERFLOWBUFLEN);
+ if (k) { /* write data if there is anything we can */
+ unix_phys_write (f,f->buf,k);
+ /* slide buffer */
+ if (i -= k) memmove (f->buf,f->buf + k,i);
+ f->bufpos = f->buf + i; /* new end of buffer */
+ }
+ }
+
+ /* Have flushed the buffer as best as possible. All done if no more
+ * data to write. Otherwise, if the buffer is empty AND if the unwritten
+ * data is larger than a chunk AND the unprotected space is also larger
+ * than a chunk, then write as many chunks as we can directly from the
+ * data. Buffer the rest, expanding the buffer as needed.
+ */
+ if (size) { /* have more data that we need to buffer? */
+ /* can write any of it to disk instead? */
+ if ((f->bufpos == f->buf) &&
+ ((j = min (f->protect - f->filepos,size)) > OVERFLOWBUFLEN)) {
+ /* write as much as we can right now */
+ unix_phys_write (f,buf,j -= (j % OVERFLOWBUFLEN));
+ buf += j; /* new data pointer */
+ size -= j; /* new data size */
+ f->curpos += j; /* advance current pointer */
+ }
+ if (size) { /* still have data that we need to buffer? */
+ /* yes, need to expand the buffer? */
+ if ((i = ((f->bufpos + size) - f->buf)) > f->buflen) {
+ /* note current position in buffer */
+ j = f->bufpos - f->buf;
+ i += OVERFLOWBUFLEN; /* yes, grow another chunk */
+ fs_resize ((void **) &f->buf,f->buflen = i - (i % OVERFLOWBUFLEN));
+ /* in case buffer relocated */
+ f->bufpos = f->buf + j;
+ }
+ /* buffer remaining data */
+ memcpy (f->bufpos,buf,size);
+ f->bufpos += size; /* new end of buffer */
+ f->curpos += size; /* advance current pointer */
+ }
+ }
+ }
+ else { /* flush buffer to disk */
+ unix_phys_write (f,f->buf,i = f->bufpos - f->buf);
+ f->bufpos = f->buf; /* reset buffer */
+ /* update positions */
+ f->curpos = f->protect = f->filepos;
+ }
+}
+
+/* Physical disk write
+ * Accepts: buffered file pointer
+ * buffer address
+ * buffer size
+ * Does not return until success
+ */
+
+void unix_phys_write (UNIXFILE *f,char *buf,size_t size)
+{
+ MAILSTREAM *stream = f->stream;
+ /* write data at desired position */
+ while (size && ((lseek (LOCAL->fd,f->filepos,L_SET) < 0) ||
+ (write (LOCAL->fd,buf,size) < 0))) {
+ int e;
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Unable to write to mailbox: %s",strerror (e = errno));
+ MM_LOG (tmp,ERROR);
+ MM_DISKERROR (NIL,e,T); /* serious problem, must retry */
+ }
+ f->filepos += size; /* update file position */
+}
+
+/* MBOX mail routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER mboxdriver = {
+ "mbox", /* driver name */
+ /* driver flags */
+ DR_LOCAL|DR_MAIL|DR_LOCKING|DR_NONEWMAILRONLY,
+ (DRIVER *) NIL, /* next driver */
+ mbox_valid, /* mailbox is valid for us */
+ unix_parameters, /* manipulate parameters */
+ unix_scan, /* scan mailboxes */
+ unix_list, /* find mailboxes */
+ unix_lsub, /* find subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ mbox_create, /* create mailbox */
+ mbox_delete, /* delete mailbox */
+ mbox_rename, /* rename mailbox */
+ mbox_status, /* status of mailbox */
+ mbox_open, /* open mailbox */
+ unix_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message structure */
+ unix_header, /* fetch message header */
+ unix_text, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ unix_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ mbox_ping, /* ping mailbox to see if still alive */
+ mbox_check, /* check for new messages */
+ mbox_expunge, /* expunge deleted messages */
+ unix_copy, /* copy messages to another mailbox */
+ mbox_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM mboxproto = {&mboxdriver};
+
+/* MBOX mail validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *mbox_valid (char *name)
+{
+ /* only INBOX, mbox must exist */
+ if (!compare_cstring (name,"INBOX") && (unix_valid ("mbox") || !errno) &&
+ (unix_valid (sysinbox()) || !errno || (errno == ENOENT)))
+ return &mboxdriver;
+ return NIL; /* can't win (yet, anyway) */
+}
+
+/* MBOX mail create mailbox
+ * Accepts: MAIL stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long mbox_create (MAILSTREAM *stream,char *mailbox)
+{
+ char tmp[MAILTMPLEN];
+ if (!compare_cstring (mailbox,"INBOX")) return unix_create (NIL,"mbox");
+ sprintf (tmp,"Can't create non-INBOX name as mbox: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+}
+
+
+/* MBOX mail delete mailbox
+ * Accepts: MAIL stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long mbox_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return mbox_rename (stream,mailbox,NIL);
+}
+
+
+/* MBOX mail rename mailbox
+ * Accepts: MAIL stream
+ * old mailbox name
+ * new mailbox name (or NIL for delete)
+ * Returns: T on success, NIL on failure
+ */
+
+long mbox_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ char tmp[MAILTMPLEN];
+ long ret = unix_rename (stream,"~/mbox",newname);
+ /* recreate file if renamed INBOX */
+ if (ret) unix_create (NIL,"mbox");
+ else MM_LOG (tmp,ERROR); /* log error */
+ return ret; /* return success */
+}
+
+/* MBOX Mail status
+ * Accepts: mail stream
+ * mailbox name
+ * status flags
+ * Returns: T on success, NIL on failure
+ */
+
+long mbox_status (MAILSTREAM *stream,char *mbx,long flags)
+{
+ MAILSTATUS status;
+ unsigned long i;
+ MAILSTREAM *tstream = NIL;
+ MAILSTREAM *systream = NIL;
+ /* make temporary stream (unless this mbx) */
+ if (!stream && !(stream = tstream =
+ mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
+ status.flags = flags; /* return status values */
+ status.messages = stream->nmsgs;
+ status.recent = stream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
+ if (!mail_elt (stream,i)->seen) status.unseen++;
+ status.uidnext = stream->uid_last + 1;
+ status.uidvalidity = stream->uid_validity;
+ if (!status.recent && /* calculate post-snarf results */
+ (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) {
+ status.messages += systream->nmsgs;
+ status.recent += systream->recent;
+ if (flags & SA_UNSEEN) /* must search to get unseen messages */
+ for (i = 1; i <= systream->nmsgs; i++)
+ if (!mail_elt (systream,i)->seen) status.unseen++;
+ /* kludge but probably good enough */
+ status.uidnext += systream->nmsgs;
+ }
+ MM_STATUS(stream,mbx,&status);/* pass status to main program */
+ if (tstream) mail_close (tstream);
+ if (systream) mail_close (systream);
+ return T; /* success */
+}
+
+/* MBOX mail open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *mbox_open (MAILSTREAM *stream)
+{
+ unsigned long i = 1;
+ unsigned long recent = 0;
+ /* return prototype for OP_PROTOTYPE call */
+ if (!stream) return &mboxproto;
+ /* change mailbox file name */
+ fs_give ((void **) &stream->mailbox);
+ stream->mailbox = cpystr ("mbox");
+ /* open mailbox, snarf new mail */
+ if (!(unix_open (stream) && mbox_ping (stream))) return NIL;
+ stream->inbox = T; /* mark that this is an INBOX */
+ /* notify upper level of mailbox sizes */
+ mail_exists (stream,stream->nmsgs);
+ while (i <= stream->nmsgs) if (mail_elt (stream,i++)->recent) ++recent;
+ mail_recent (stream,recent); /* including recent messages */
+ return stream;
+}
+
+/* MBOX mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+static int snarfed = 0; /* number of snarfs */
+
+long mbox_ping (MAILSTREAM *stream)
+{
+ int sfd;
+ unsigned long size;
+ struct stat sbuf;
+ char *s;
+ DOTLOCK lock,lockx;
+ /* time to try snarf and sysinbox non-empty? */
+ if (LOCAL && !stream->rdonly && !stream->lock &&
+ (time (0) >= (LOCAL->lastsnarf +
+ (long) mail_parameters (NIL,GET_SNARFINTERVAL,NIL))) &&
+ !stat (sysinbox (),&sbuf) && sbuf.st_size) {
+ MM_CRITICAL (stream); /* yes, go critical */
+ /* open and lock sysinbox */
+ if ((sfd = unix_lock (sysinbox (),O_RDWR,
+ (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
+ &lockx,LOCK_EX)) >= 0) {
+ /* locked sysinbox in good format? */
+ if (fstat (sfd,&sbuf) || !(size = sbuf.st_size) ||
+ !unix_isvalid_fd (sfd)) {
+ sprintf (LOCAL->buf,"Mail drop %s is not in standard Unix format",
+ sysinbox ());
+ MM_LOG (LOCAL->buf,ERROR);
+ }
+ /* sysinbox good, parse and excl-lock mbox */
+ else if (unix_parse (stream,&lock,LOCK_EX)) {
+ lseek (sfd,0,L_SET); /* read entire sysinbox into memory */
+ read (sfd,s = (char *) fs_get (size + 1),size);
+ s[size] = '\0'; /* tie it off */
+ /* append to end of mbox */
+ lseek (LOCAL->fd,LOCAL->filesize,L_SET);
+
+ /* copy to mbox */
+ if ((write (LOCAL->fd,s,size) < 0) || fsync (LOCAL->fd)) {
+ sprintf (LOCAL->buf,"New mail move failed: %s",strerror (errno));
+ MM_LOG (LOCAL->buf,WARN);
+ /* revert mbox to previous size */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ }
+ /* sysinbox better not have changed */
+ else if (fstat (sfd,&sbuf) || (size != sbuf.st_size)) {
+ sprintf (LOCAL->buf,"Mail drop %s lock failure, old=%lu now=%lu",
+ sysinbox (),size,(unsigned long) sbuf.st_size);
+ MM_LOG (LOCAL->buf,ERROR);
+ /* revert mbox to previous size */
+ ftruncate (LOCAL->fd,LOCAL->filesize);
+ /* Believe it or not, a Singaporean government system actually had
+ * symlinks from /var/mail/user to ~user/mbox. To compound this
+ * error, they used an SVR4 system; BSD and OSF locks would have
+ * prevented it but not SVR4 locks.
+ */
+ if (!fstat (sfd,&sbuf) && (size == sbuf.st_size))
+ syslog (LOG_ALERT,"File %s and %s are the same file!",
+ sysinbox (),stream->mailbox);
+ }
+ else { /* data copied OK */
+ ftruncate (sfd,0); /* truncate sysinbox to zero bytes */
+ if (!snarfed++) { /* have we snarfed before? */
+ /* syslog if server, else user log */
+ sprintf (LOCAL->buf,"Moved %lu bytes of new mail to %s from %s",
+ size,stream->mailbox,sysinbox ());
+ if (strcmp ((char *) mail_parameters (NIL,GET_SERVICENAME,NIL),
+ "unknown"))
+ syslog (LOG_INFO,"%s host= %s",LOCAL->buf,tcp_clienthost ());
+ else MM_LOG (LOCAL->buf,WARN);
+ }
+ }
+ /* done with sysinbox text */
+ fs_give ((void **) &s);
+ /* all done with mbox */
+ unix_unlock (LOCAL->fd,stream,&lock);
+ mail_unlock (stream); /* unlock the stream */
+ MM_NOCRITICAL (stream); /* done with critical */
+ }
+ /* all done with sysinbox */
+ unix_unlock (sfd,NIL,&lockx);
+ }
+ MM_NOCRITICAL (stream); /* done with critical */
+ LOCAL->lastsnarf = time (0);/* note time of last snarf */
+ }
+ return unix_ping (stream); /* do the unix routine now */
+}
+
+/* MBOX mail check mailbox
+ * Accepts: MAIL stream
+ */
+
+void mbox_check (MAILSTREAM *stream)
+{
+ /* do local ping, then do unix routine */
+ if (mbox_ping (stream)) unix_check (stream);
+}
+
+
+/* MBOX mail expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long mbox_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ long ret = unix_expunge (stream,sequence,options);
+ mbox_ping (stream); /* do local ping */
+ return ret;
+}
+
+
+/* MBOX mail append message from stringstruct
+ * Accepts: MAIL stream
+ * destination mailbox
+ * append callback
+ * data for callback
+ * Returns: T if append successful, else NIL
+ */
+
+long mbox_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ char tmp[MAILTMPLEN];
+ if (mbox_valid (mailbox)) return unix_append (stream,"mbox",af,data);
+ sprintf (tmp,"Can't append to that name: %.80s",mailbox);
+ MM_LOG (tmp,ERROR);
+ return NIL;
+}
diff --git a/imap/src/osdep/unix/unix.h b/imap/src/osdep/unix/unix.h
new file mode 100644
index 00000000..1305184f
--- /dev/null
+++ b/imap/src/osdep/unix/unix.h
@@ -0,0 +1,161 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX mail routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 20 December 1989
+ * Last Edited: 30 August 2006
+ */
+
+
+/* DEDICATION
+ *
+ * This file is dedicated to my dog, Unix, also known as Yun-chan and
+ * Unix J. Terwilliker Jehosophat Aloysius Monstrosity Animal Beast. Unix
+ * passed away at the age of 11 1/2 on September 14, 1996, 12:18 PM PDT, after
+ * a two-month bout with cirrhosis of the liver.
+ *
+ * He was a dear friend, and I miss him terribly.
+ *
+ * Lift a leg, Yunie. Luv ya forever!!!!
+ */
+
+/* Validate line
+ * Accepts: pointer to candidate string to validate as a From header
+ * return pointer to end of date/time field
+ * return pointer to offset from t of time (hours of ``mmm dd hh:mm'')
+ * return pointer to offset from t of time zone (if non-zero)
+ * Returns: t,ti,zn set if valid From string, else ti is NIL
+ */
+
+#define VALID(s,x,ti,zn) { \
+ ti = 0; \
+ if ((*s == 'F') && (s[1] == 'r') && (s[2] == 'o') && (s[3] == 'm') && \
+ (s[4] == ' ')) { \
+ for (x = s + 5; *x && *x != '\012'; x++); \
+ if (*x) { \
+ if (x[-1] == '\015') --x; \
+ if (x - s >= 41) { \
+ for (zn = -1; x[zn] != ' '; zn--); \
+ if ((x[zn-1] == 'm') && (x[zn-2] == 'o') && (x[zn-3] == 'r') && \
+ (x[zn-4] == 'f') && (x[zn-5] == ' ') && (x[zn-6] == 'e') && \
+ (x[zn-7] == 't') && (x[zn-8] == 'o') && (x[zn-9] == 'm') && \
+ (x[zn-10] == 'e') && (x[zn-11] == 'r') && (x[zn-12] == ' '))\
+ x += zn - 12; \
+ } \
+ if (x - s >= 27) { \
+ if (x[-5] == ' ') { \
+ if (x[-8] == ':') zn = 0,ti = -5; \
+ else if (x[-9] == ' ') ti = zn = -9; \
+ else if ((x[-11] == ' ') && ((x[-10]=='+') || (x[-10]=='-'))) \
+ ti = zn = -11; \
+ } \
+ else if (x[-4] == ' ') { \
+ if (x[-9] == ' ') zn = -4,ti = -9; \
+ } \
+ else if (x[-6] == ' ') { \
+ if ((x[-11] == ' ') && ((x[-5] == '+') || (x[-5] == '-'))) \
+ zn = -6,ti = -11; \
+ } \
+ if (ti && !((x[ti - 3] == ':') && \
+ (x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') && \
+ (x[ti - 3] == ' ') && (x[ti - 7] == ' ') && \
+ (x[ti - 11] == ' '))) ti = 0; \
+ } \
+ } \
+ } \
+}
+
+/* You are not expected to understand this macro, but read the next page if
+ * you are not faint of heart.
+ *
+ * Known formats to the VALID macro are:
+ * From user Wed Dec 2 05:53 1992
+ * BSD From user Wed Dec 2 05:53:22 1992
+ * SysV From user Wed Dec 2 05:53 PST 1992
+ * rn From user Wed Dec 2 05:53:22 PST 1992
+ * From user Wed Dec 2 05:53 -0700 1992
+ * emacs From user Wed Dec 2 05:53:22 -0700 1992
+ * From user Wed Dec 2 05:53 1992 PST
+ * From user Wed Dec 2 05:53:22 1992 PST
+ * From user Wed Dec 2 05:53 1992 -0700
+ * Solaris From user Wed Dec 2 05:53:22 1992 -0700
+ *
+ * Plus all of the above with `` remote from xxx'' after it. Thank you very
+ * much, smail and Solaris, for making my life considerably more complicated.
+ */
+
+/*
+ * What? You want to understand the VALID macro anyway? Alright, since you
+ * insist. Actually, it isn't really all that difficult, provided that you
+ * take it step by step.
+ *
+ * Line 1 Initializes the return ti value to failure (0);
+ * Lines 2-3 Validates that the 1st-5th characters are ``From ''.
+ * Lines 4-6 Validates that there is an end of line and points x at it.
+ * Lines 7-14 First checks to see if the line is at least 41 characters long.
+ * If so, it scans backwards to find the rightmost space. From
+ * that point, it scans backwards to see if the string matches
+ * `` remote from''. If so, it sets x to point to the space at
+ * the start of the string.
+ * Line 15 Makes sure that there are at least 27 characters in the line.
+ * Lines 16-21 Checks if the date/time ends with the year (there is a space
+ * five characters back). If there is a colon three characters
+ * further back, there is no timezone field, so zn is set to 0
+ * and ti is set in front of the year. Otherwise, there must
+ * either to be a space four characters back for a three-letter
+ * timezone, or a space six characters back followed by a + or -
+ * for a numeric timezone; in either case, zn and ti become the
+ * offset of the space immediately before it.
+ * Lines 22-24 Are the failure case for line 14. If there is a space four
+ * characters back, it is a three-letter timezone; there must be a
+ * space for the year nine characters back. zn is the zone
+ * offset; ti is the offset of the space.
+ * Lines 25-28 Are the failure case for line 20. If there is a space six
+ * characters back, it is a numeric timezone; there must be a
+ * space eleven characters back and a + or - five characters back.
+ * zn is the zone offset; ti is the offset of the space.
+ * Line 29-32 If ti is valid, make sure that the string before ti is of the
+ * form www mmm dd hh:mm or www mmm dd hh:mm:ss, otherwise
+ * invalidate ti. There must be a colon three characters back
+ * and a space six or nine characters back (depending upon
+ * whether or not the character six characters back is a colon).
+ * There must be a space three characters further back (in front
+ * of the day), one seven characters back (in front of the month),
+ * and one eleven characters back (in front of the day of week).
+ * ti is set to be the offset of the space before the time.
+ *
+ * Why a macro? It gets invoked a *lot* in a tight loop. On some of the
+ * newer pipelined machines it is faster being open-coded than it would be if
+ * subroutines are called.
+ *
+ * Why does it scan backwards from the end of the line, instead of doing the
+ * much easier forward scan? There is no deterministic way to parse the
+ * ``user'' field, because it may contain unquoted spaces! Yes, I tested it to
+ * see if unquoted spaces were possible. They are, and I've encountered enough
+ * evil mail to be totally unwilling to trust that ``it will never happen''.
+ */
+
+/* Build parameters */
+
+#define KODRETRY 15 /* kiss-of-death retry in seconds */
+#define LOCKTIMEOUT 5 /* lock timeout in minutes */
diff --git a/imap/src/osdep/unix/utime.c b/imap/src/osdep/unix/utime.c
new file mode 100644
index 00000000..6fef2d36
--- /dev/null
+++ b/imap/src/osdep/unix/utime.c
@@ -0,0 +1,45 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: BSD utime() emulator
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 10 October 1996
+ * Last Edited: 30 August 2006
+ */
+
+#undef utime
+
+/* Portable utime() that takes it args like real Unix systems
+ * Accepts: file path
+ * traditional utime() argument
+ * Returns: utime() results
+ */
+
+int portable_utime (char *file,time_t timep[2])
+{
+ struct utimbuf times;
+ /* in case there's other cruft there */
+ memset (&times,0,sizeof (struct utimbuf));
+ times.actime = timep[0]; /* copy the portable values */
+ times.modtime = timep[1];
+ return utime (file,&times); /* now call the SVR4 routine */
+}
diff --git a/imap/src/osdep/unix/write.c b/imap/src/osdep/unix/write.c
new file mode 100644
index 00000000..c7854815
--- /dev/null
+++ b/imap/src/osdep/unix/write.c
@@ -0,0 +1,59 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Write data, treating partial writes as an error
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 26 May 1995
+ * Last Edited: 30 August 2006
+ */
+
+/* The whole purpose of this unfortunate routine is to deal with DOS and
+ * certain cretinous versions of UNIX which decided that the "bytes actually
+ * written" return value from write() gave them license to use that for things
+ * that are really errors, such as disk quota exceeded, maximum file size
+ * exceeded, disk full, etc.
+ *
+ * BSD won't screw us this way on the local filesystem, but who knows what
+ * some NFS-mounted filesystem will do.
+ */
+
+#undef write
+
+/* Write data to file
+ * Accepts: file descriptor
+ * I/O vector structure
+ * number of vectors in structure
+ * Returns: number of bytes written if successful, -1 if failure
+ */
+
+long maxposint = (long)((((unsigned long) 1) << ((sizeof(int) * 8) - 1)) - 1);
+
+long safe_write (int fd,char *buf,long nbytes)
+{
+ long i,j;
+ if (nbytes > 0) for (i = nbytes; i; i -= j,buf += j) {
+ while (((j = write (fd,buf,(int) min (maxposint,i))) < 0) &&
+ (errno == EINTR));
+ if (j < 0) return j;
+ }
+ return nbytes;
+}
diff --git a/imap/src/osdep/vms/build.com b/imap/src/osdep/vms/build.com
new file mode 100644
index 00000000..aedbcc82
--- /dev/null
+++ b/imap/src/osdep/vms/build.com
@@ -0,0 +1,99 @@
+$! ========================================================================
+$! Copyright 1988-2006 University of Washington
+$!
+$! Licensed under the Apache License, Version 2.0 (the "License");
+$! you may not use this file except in compliance with the License.
+$! You may obtain a copy of the License at
+$!
+$! http://www.apache.org/licenses/LICENSE-2.0
+$!
+$!
+$! ========================================================================
+$!
+$! Program: Portable c-client build for VMS
+$!
+$! Author: Mark Crispin
+$! Networks and Distributed Computing
+$! Computing & Communications
+$! University of Washington
+$! Administration Building, AG-44
+$! Seattle, WA 98195
+$! Internet: MRC@CAC.Washington.EDU
+$!
+$! Date: 2 August 1994
+$! Last Edited: 30 August 2006
+$!
+$! Change this to your local timezone. This value is the number of minutes
+$! east of UTC (formerly known as GMT). Sample values: -300 (US east coast),
+$! -480 (US west coast), 540 (Japan), 60 (western Europe).
+$! VAX C's HELP information says that you should be able to use gmtime(), but
+$! it returns 0 for the struct. ftime(), you ask? It, too, returns 0 for a
+$! timezone. Nothing sucks like a VAX!
+$!
+$ CC_TIMEZONE=-480
+$!
+$! CC options
+$!
+$ CC_PREF = "/OPTIMIZE/INCLUDE=[]"
+$ CC_PREF = CC_PREF + "/DEFINE=net_getbuffer=NET_GETBUF"
+$ CC_PREF = CC_PREF + "/DEFINE=LOCALTIMEZONE='CC_TIMEZONE'"
+$!
+$! Determine TCP type
+$!
+$ TCP_TYPE = "VMSN" ! default to none
+$ IF F$LOCATE("MULTINET", P1) .LT. F$LENGTH(P1)
+$ THEN
+$ DEFINE SYS MULTINET_ROOT:[MULTINET.INCLUDE.SYS],sys$library
+$ DEFINE NETINET MULTINET_ROOT:[MULTINET.INCLUDE.NETINET]
+$ DEFINE ARPA MULTINET_ROOT:[MULTINET.INCLUDE.ARPA]
+$ TCP_TYPE = "VMSM" ! Multinet
+$ LINK_OPT = ",LINK_MNT/OPTION"
+$ ENDIF
+$ IF F$LOCATE("NETLIB", P1) .LT. F$LENGTH(P1)
+$ THEN
+$ DEFINE SYS SYS$LIBRARY: ! normal .H location
+$ DEFINE NETINET SYS$LIBRARY:
+$ DEFINE ARPA SYS$LIBRARY:
+$ LINK_OPT = ",LINK_NLB/OPTION"
+$ TCP_TYPE = "VMSL" ! NETLIB
+$ ENDIF
+$ IF TCP_TYPE .EQS. "VMSN"
+$ THEN
+$ DEFINE SYS SYS$LIBRARY: ! normal .H location
+$ DEFINE NETINET SYS$LIBRARY:
+$ DEFINE ARPA SYS$LIBRARY:
+$ LINK_OPT = ""
+$ ENDIF
+$!
+$ COPY TCP_'TCP_TYPE'.C TCP_VMS.C;
+$!
+$ COPY OS_VMS.H OSDEP.H;
+$ SET VERIFY
+$ CC'CC_PREF' MAIL
+$ CC'CC_PREF' IMAP4R1
+$ CC'CC_PREF' SMTP
+$ CC'CC_PREF' NNTP
+$ CC'CC_PREF' POP3
+$ CC'CC_PREF' DUMMYVMS
+$ CC'CC_PREF' RFC822
+$ CC'CC_PREF' MISC
+$ CC'CC_PREF' OS_VMS
+$ CC'CC_PREF' SMANAGER
+$ CC'CC_PREF' FLSTRING
+$ CC'CC_PREF' NEWSRC
+$ CC'CC_PREF' NETMSG
+$ CC'CC_PREF' UTF8
+$ CC'CC_PREF' UTF8AUX
+$ CC'CC_PREF' MTEST
+$ CC'CC_PREF' MAILUTIL
+$!
+$ LINK MTEST,OS_VMS,MAIL,IMAP4R1,SMTP,NNTP,POP3,DUMMYVMS,RFC822,MISC,UTF8,-
+ UTF8AUX,SMANAGER,FLSTRING,NEWSRC,NETMSG,-
+ SYS$INPUT:/OPTION'LINK_OPT',LINK/OPTION
+PSECT=_CTYPE_,NOWRT
+$ LINK MAILUTIL,OS_VMS,MAIL,IMAP4R1,SMTP,NNTP,POP3,DUMMYVMS,RFC822,MISC,UTF8,-
+ UTF8AUX,SMANAGER,FLSTRING,NEWSRC,NETMSG,-
+ SYS$INPUT:/OPTION'LINK_OPT',LINK/OPTION
+PSECT=_CTYPE_,NOWRT
+$ SET NOVERIFY
+$ EXIT
diff --git a/imap/src/osdep/vms/clean.com b/imap/src/osdep/vms/clean.com
new file mode 100644
index 00000000..98df0db5
--- /dev/null
+++ b/imap/src/osdep/vms/clean.com
@@ -0,0 +1,26 @@
+$! ========================================================================
+$! Copyright 1988-2006 University of Washington
+$!
+$! Licensed under the Apache License, Version 2.0 (the "License");
+$! you may not use this file except in compliance with the License.
+$! You may obtain a copy of the License at
+$!
+$! http://www.apache.org/licenses/LICENSE-2.0
+$!
+$!
+$! ========================================================================
+$!
+$! Program: Portable c-client cleanup for VMS
+$!
+$! Author: Mark Crispin
+$! Networks and Distributed Computing
+$! Computing & Communications
+$! University of Washington
+$! Administration Building, AG-44
+$! Seattle, WA 98195
+$! Internet: MRC@CAC.Washington.EDU
+$!
+$! Date: 14 June 1995
+$! Last Edited: 30 August 2006
+$!
+$ DELETE *.OBJ;*,OSDEP.*;*,TCP_VMS.C;*,MTEST.EXE;*,MAILUTIL.EXE;*
diff --git a/imap/src/osdep/vms/dummy.h b/imap/src/osdep/vms/dummy.h
new file mode 100644
index 00000000..32650e06
--- /dev/null
+++ b/imap/src/osdep/vms/dummy.h
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 9 May 1991
+ * Last Edited: 30 August 2006
+ */
+
+/* Exported function prototypes */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat);
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long scan_contents (DRIVER *dtb,char *name,char *contents,
+ unsigned long csiz,unsigned long fsiz);
+long dummy_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz);
+long dummy_create (MAILSTREAM *stream,char *mailbox);
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode);
+long dummy_delete (MAILSTREAM *stream,char *mailbox);
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname);
+char *dummy_file (char *dst,char *name);
+long dummy_canonicalize (char *tmp,char *ref,char *pat);
diff --git a/imap/src/osdep/vms/dummyvms.c b/imap/src/osdep/vms/dummyvms.c
new file mode 100644
index 00000000..cfcb45d0
--- /dev/null
+++ b/imap/src/osdep/vms/dummyvms.c
@@ -0,0 +1,295 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines for VMS
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 24 May 1993
+ * Last Edited: 30 August 2006
+ */
+
+
+#include <ctype.h>
+#include <stdio.h>
+#include "mail.h"
+#include "osdep.h"
+#include "dummy.h"
+#include "misc.h"
+
+/* Function prototypes */
+
+DRIVER *dummy_valid (char *name);
+void *dummy_parameters (long function,void *value);
+MAILSTREAM *dummy_open (MAILSTREAM *stream);
+void dummy_close (MAILSTREAM *stream,long options);
+long dummy_ping (MAILSTREAM *stream);
+void dummy_check (MAILSTREAM *stream);
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options);
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+/* Dummy routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER dummydriver = {
+ "dummy", /* driver name */
+ DR_LOCAL|DR_MAIL, /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ dummy_valid, /* mailbox is valid for us */
+ dummy_parameters, /* manipulate parameters */
+ dummy_scan, /* scan mailboxes */
+ dummy_list, /* list mailboxes */
+ dummy_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ dummy_delete, /* delete mailbox */
+ dummy_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ dummy_open, /* open mailbox */
+ dummy_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message structure */
+ NIL, /* fetch header */
+ NIL, /* fetch text */
+ NIL, /* fetch message data */
+ NIL, /* unique identifier */
+ NIL, /* message number from UID */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ dummy_ping, /* ping mailbox to see if still alive */
+ dummy_check, /* check for new messages */
+ dummy_expunge, /* expunge deleted messages */
+ dummy_copy, /* copy messages to another mailbox */
+ dummy_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+
+ /* prototype stream */
+MAILSTREAM dummyproto = {&dummydriver};
+
+/* Dummy validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *dummy_valid (char *name)
+{
+ char tmp[MAILTMPLEN];
+ /* must be valid local mailbox */
+ return (name && *name && (*name != '{') && !compare_cstring (name,"INBOX")) ?
+ &dummydriver : NIL;
+}
+
+
+/* Dummy manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *dummy_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+/* Dummy scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ /* return silently */
+}
+
+
+/* Dummy list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ /* return silently */
+}
+
+
+/* Dummy list subscribed mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ /* return silently */
+}
+
+/* Dummy create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * driver type to use
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create (MAILSTREAM *stream,char *mailbox)
+{
+ return NIL; /* always fails */
+}
+
+
+/* Dummy delete mailbox
+ * Accepts: mail stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return NIL; /* always fails */
+}
+
+
+/* Mail rename mailbox
+ * Accepts: mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ return NIL; /* always fails */
+}
+
+/* Dummy open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *dummy_open (MAILSTREAM *stream)
+{
+ char tmp[MAILTMPLEN];
+ /* OP_PROTOTYPE call or silence */
+ if (!stream || stream->silent) return NIL;
+ if (compare_cstring (stream->mailbox,"INBOX")) {
+ sprintf (tmp,"Not a mailbox: %s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ return NIL; /* always fails */
+ }
+ if (!stream->silent) { /* only if silence not requested */
+ mail_exists (stream,0); /* say there are 0 messages */
+ mail_recent (stream,0);
+ stream->uid_validity = time (0);
+ }
+ stream->inbox = T; /* note that it's an INBOX */
+ return stream; /* return success */
+}
+
+
+/* Dummy close
+ * Accepts: MAIL stream
+ * options
+ */
+
+void dummy_close (MAILSTREAM *stream,long options)
+{
+ /* return silently */
+}
+
+/* Dummy ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+long dummy_ping (MAILSTREAM *stream)
+{
+ return T;
+}
+
+
+/* Dummy check mailbox
+ * Accepts: MAIL stream
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+void dummy_check (MAILSTREAM *stream)
+{
+ dummy_ping (stream); /* invoke ping */
+}
+
+
+/* Dummy expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ return LONGT;
+}
+
+/* Dummy copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * options
+ * Returns: T if copy successful, else NIL
+ */
+
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) fatal ("Impossible dummy_copy");
+ return NIL;
+}
+
+
+/* Dummy append message string
+ * Accepts: mail stream
+ * destination mailbox
+ * append callback function
+ * data for callback
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Can't append to %s",mailbox);
+ mm_log (tmp,ERROR); /* pass up error */
+ return NIL; /* always fails */
+}
diff --git a/imap/src/osdep/vms/env_vms.c b/imap/src/osdep/vms/env_vms.c
new file mode 100644
index 00000000..56b7a223
--- /dev/null
+++ b/imap/src/osdep/vms/env_vms.c
@@ -0,0 +1,174 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: VMS environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 2 August 1994
+ * Last Edited: 30 August 2006
+ */
+
+
+static char *myUserName = NIL; /* user name */
+static char *myLocalHost = NIL; /* local host name */
+static char *myHomeDir = NIL; /* home directory name */
+static char *myNewsrc = NIL; /* newsrc file name */
+
+#include "pmatch.c" /* include wildcard pattern matcher */
+
+/* Environment manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *env_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_USERNAME:
+ myUserName = cpystr ((char *) value);
+ case GET_USERNAME:
+ ret = (void *) myUserName;
+ break;
+ case SET_HOMEDIR:
+ myHomeDir = cpystr ((char *) value);
+ case GET_HOMEDIR:
+ ret = (void *) myHomeDir;
+ break;
+ case SET_LOCALHOST:
+ myLocalHost = cpystr ((char *) value);
+ case GET_LOCALHOST:
+ ret = (void *) myLocalHost;
+ break;
+ case SET_NEWSRC:
+ if (myNewsrc) fs_give ((void **) &myNewsrc);
+ myNewsrc = cpystr ((char *) value);
+ case GET_NEWSRC:
+ if (!myNewsrc) { /* set news file name if not defined */
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"%s:.newsrc",myhomedir ());
+ myNewsrc = cpystr (tmp);
+ }
+ ret = (void *) myNewsrc;
+ break;
+ }
+ return ret;
+}
+
+/* Write current time
+ * Accepts: destination string
+ * optional format of day-of-week prefix
+ * format of date and time
+ */
+
+static void do_date (char *date,char *prefix,char *fmt)
+{
+ time_t tn = time (0);
+ struct tm *t = localtime (&tn);
+ int zone = LOCALTIMEZONE + (t->tm_isdst ? 60 : 0);
+ if (prefix) { /* want day of week? */
+ sprintf (date,prefix,days[t->tm_wday]);
+ date += strlen (date); /* make next sprintf append */
+ }
+ /* output the date */
+ sprintf (date,fmt,t->tm_mday,months[t->tm_mon],t->tm_year+1900,
+ t->tm_hour,t->tm_min,t->tm_sec,zone/60,abs (zone) % 60);
+}
+
+
+/* Write current time in RFC 822 format
+ * Accepts: destination string
+ */
+
+void rfc822_date (char *date)
+{
+ do_date (date,"%s, ","%d %s %d %02d:%02d:%02d %+03d%02d");
+}
+
+
+/* Write current time in internal format
+ * Accepts: destination string
+ */
+
+void internal_date (char *date)
+{
+ do_date (date,NIL,"%02d-%s-%d %02d:%02d:%02d %+03d%02d");
+}
+
+/* Return my user name
+ * Returns: my user name
+ */
+
+char *myusername ()
+{
+ struct stat sbuf;
+ char tmp[MAILTMPLEN];
+
+ if (!myUserName) { /* get user name if don't have it yet */
+ myUserName = cpystr (cuserid (NIL));
+ myHomeDir = cpystr ("SYS$LOGIN");
+ }
+ return myUserName;
+}
+
+
+/* Return my home directory name
+ * Returns: my home directory name
+ */
+
+char *myhomedir ()
+{
+ if (!myHomeDir) myusername ();/* initialize if first time */
+ return myHomeDir;
+}
+
+
+/* Determine default prototype stream to user
+ * Accepts: type (NIL for create, T for append)
+ * Returns: default prototype stream
+ */
+
+MAILSTREAM *default_proto (long type)
+{
+ return NIL; /* no default prototype */
+}
+
+/* Emulator for BSD syslog() routine
+ * Accepts: priority
+ * message
+ * parameters
+ */
+
+void syslog (int priority,const char *message,...)
+{
+}
+
+
+/* Emulator for BSD openlog() routine
+ * Accepts: identity
+ * options
+ * facility
+ */
+
+void openlog (const char *ident,int logopt,int facility)
+{
+}
diff --git a/imap/src/osdep/vms/env_vms.h b/imap/src/osdep/vms/env_vms.h
new file mode 100644
index 00000000..59160a06
--- /dev/null
+++ b/imap/src/osdep/vms/env_vms.h
@@ -0,0 +1,60 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: VMS environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 2 August 1994
+ * Last Edited: 30 August 2006
+ */
+
+
+#define SUBSCRIPTIONFILE(t) sprintf (t,"%s\\SUBSCRIPTIONS.TXT",myhomedir ())
+#define SUBSCRIPTIONTEMP(t) sprintf (t,"%s\\SUBSCRIPTIONS.TMP",myhomedir ())
+
+/* Function prototypes */
+
+#include "env.h"
+
+char *myusername ();
+
+
+/* syslog() emulation */
+
+#define LOG_MAIL (2<<3) /* mail system */
+#define LOG_DAEMON (3<<3) /* system daemons */
+#define LOG_AUTH (4<<3) /* security/authorization messages */
+#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 signification condition */
+#define LOG_INFO 6 /* informational */
+#define LOG_DEBUG 7 /* debug-level messages */
+#define LOG_PID 0x01 /* log the pid with each message */
+#define LOG_CONS 0x02 /* log on the console if errors in sending */
+#define LOG_ODELAY 0x04 /* delay open until syslog() is called */
+#define LOG_NDELAY 0x08 /* don't delay open */
+#define LOG_NOWAIT 0x10 /* if forking to log on console, don't wait() */
+
+void openlog (const char *ident,int logopt,int facility);
+void syslog (int priority,const char *message,...);
diff --git a/imap/src/osdep/vms/fs_vms.c b/imap/src/osdep/vms/fs_vms.c
new file mode 100644
index 00000000..03908328
--- /dev/null
+++ b/imap/src/osdep/vms/fs_vms.c
@@ -0,0 +1,62 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Free storage management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Get a block of free storage
+ * Accepts: size of desired block
+ * Returns: free storage block
+ */
+
+void *fs_get (size_t size)
+{
+ void *block = malloc (size ? size : (size_t) 1);
+ if (!block) fatal ("Out of memory");
+ return (block);
+}
+
+
+/* Resize a block of free storage
+ * Accepts: ** pointer to current block
+ * new size
+ */
+
+void fs_resize (void **block,size_t size)
+{
+ if (!(*block = realloc (*block,size ? size : (size_t) 1)))
+ fatal ("Can't resize memory");
+}
+
+
+/* Return a block of free storage
+ * Accepts: ** pointer to free storage block
+ */
+
+void fs_give (void **block)
+{
+ free (*block);
+ *block = NIL;
+}
diff --git a/imap/src/osdep/vms/ftl_vms.c b/imap/src/osdep/vms/ftl_vms.c
new file mode 100644
index 00000000..9e65ef55
--- /dev/null
+++ b/imap/src/osdep/vms/ftl_vms.c
@@ -0,0 +1,38 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: DOS/VMS/TOPS-20 crash management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Report a fatal error
+ * Accepts: string to output
+ */
+
+void fatal (char *string)
+{
+ mm_fatal (string); /* pass up the string */
+ abort (); /* die horribly */
+}
diff --git a/imap/src/osdep/vms/link.opt b/imap/src/osdep/vms/link.opt
new file mode 100644
index 00000000..0f454490
--- /dev/null
+++ b/imap/src/osdep/vms/link.opt
@@ -0,0 +1 @@
+SYS$SHARE:VAXCRTL/SHARE
diff --git a/imap/src/osdep/vms/link_mnt.opt b/imap/src/osdep/vms/link_mnt.opt
new file mode 100644
index 00000000..5eb99104
--- /dev/null
+++ b/imap/src/osdep/vms/link_mnt.opt
@@ -0,0 +1 @@
+MULTINET:MULTINET_SOCKET_LIBRARY/SHARE
diff --git a/imap/src/osdep/vms/link_nlb.opt b/imap/src/osdep/vms/link_nlb.opt
new file mode 100644
index 00000000..883d2859
--- /dev/null
+++ b/imap/src/osdep/vms/link_nlb.opt
@@ -0,0 +1 @@
+NETLIB_SHR/SHARE
diff --git a/imap/src/osdep/vms/linkage.c b/imap/src/osdep/vms/linkage.c
new file mode 100644
index 00000000..14490a0c
--- /dev/null
+++ b/imap/src/osdep/vms/linkage.c
@@ -0,0 +1,37 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Default driver linkage
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 13 June 1995
+ * Last Edited: 23 May 2007
+ */
+
+ mail_link (&imapdriver); /* link in the imap driver */
+ mail_link (&nntpdriver); /* link in the nntp driver */
+ mail_link (&pop3driver); /* link in the pop3 driver */
+ mail_link (&dummydriver); /* link in the dummy driver */
+ auth_link (&auth_ext); /* link in the ext authenticator */
+ auth_link (&auth_md5); /* link in the md5 authenticator */
+ auth_link (&auth_pla); /* link in the plain authenticator */
+ auth_link (&auth_log); /* link in the log authenticator */
+ mail_versioncheck (CCLIENTVERSION); /* validate version */
diff --git a/imap/src/osdep/vms/linkage.h b/imap/src/osdep/vms/linkage.h
new file mode 100644
index 00000000..7e0eccfa
--- /dev/null
+++ b/imap/src/osdep/vms/linkage.h
@@ -0,0 +1,36 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Default driver linkage
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 13 June 1995
+ * Last Edited: 30 August 2006
+ */
+
+extern DRIVER imapdriver;
+extern DRIVER nntpdriver;
+extern DRIVER pop3driver;
+extern DRIVER dummydriver;
+extern AUTHENTICATOR auth_ext;
+extern AUTHENTICATOR auth_log;
+extern AUTHENTICATOR auth_md5;
+extern AUTHENTICATOR auth_pla;
diff --git a/imap/src/osdep/vms/nl_vms.c b/imap/src/osdep/vms/nl_vms.c
new file mode 100644
index 00000000..b2d5616a
--- /dev/null
+++ b/imap/src/osdep/vms/nl_vms.c
@@ -0,0 +1,92 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: UNIX/VMS newline routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Copy string with CRLF newlines
+ * Accepts: destination string
+ * pointer to size of destination string buffer
+ * source string
+ * length of source string
+ * Returns: length of copied string
+ */
+
+unsigned long strcrlfcpy (unsigned char **dst,unsigned long *dstl,
+ unsigned char *src,unsigned long srcl)
+{
+ long i = srcl * 2,j;
+ unsigned char c,*d = src;
+ if (*dst) { /* candidate destination provided? */
+ /* count NLs if doesn't fit worst-case */
+ if (i > *dstl) for (i = j = srcl; j; --j) if (*d++ == '\012') i++;
+ /* still too small, must reset destination */
+ if (i > *dstl) fs_give ((void **) dst);
+ }
+ /* make a new buffer if needed */
+ if (!*dst) *dst = (char *) fs_get ((*dstl = i) + 1);
+ d = *dst; /* destination string */
+ if (srcl) do { /* main copy loop */
+ if ((c = *src++) < '\016') {
+ /* prepend CR to LF */
+ if (c == '\012') *d++ = '\015';
+ /* unlikely CR */
+ else if ((c == '\015') && (srcl > 1) && (*src == '\012')) {
+ *d++ = c; /* copy the CR */
+ c = *src++; /* grab the LF */
+ --srcl; /* adjust the count */
+ }
+ }
+ *d++ = c; /* copy character */
+ } while (--srcl);
+ *d = '\0'; /* tie off destination */
+ return d - *dst; /* return length */
+}
+
+/* Length of string after strcrlfcpy applied
+ * Accepts: source string
+ * Returns: length of string
+ */
+
+unsigned long strcrlflen (STRING *s)
+{
+ unsigned long pos = GETPOS (s);
+ unsigned long i = SIZE (s);
+ unsigned long j = i;
+ while (j--) switch (SNX (s)) {/* search for newlines */
+ case '\015': /* unlikely carriage return */
+ if (j && (CHR (s) == '\012')) {
+ SNX (s); /* eat the line feed */
+ j--;
+ }
+ break;
+ case '\012': /* line feed? */
+ i++;
+ default: /* ordinary chararacter */
+ break;
+ }
+ SETPOS (s,pos); /* restore old position */
+ return i;
+}
diff --git a/imap/src/osdep/vms/os_vms.c b/imap/src/osdep/vms/os_vms.c
new file mode 100644
index 00000000..cd333ba9
--- /dev/null
+++ b/imap/src/osdep/vms/os_vms.c
@@ -0,0 +1,76 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- VMS version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 2 August 1994
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_vms.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "misc.h"
+
+
+#include "fs_vms.c"
+#include "ftl_vms.c"
+#include "nl_vms.c"
+#include "env_vms.c"
+#include "tcp_vms.c"
+
+#define server_login(user,pass,authuser,argc,argv) NIL
+#define authserver_login(user,authuser,argc,argv) NIL
+#define myusername() "" /* dummy definition to prevent build errors */
+#define MD5ENABLE ""
+
+#include "auth_md5.c"
+#include "auth_pla.c"
+#include "auth_log.c"
+
+
+/* Emulator for UNIX getpass() call
+ * Accepts: prompt
+ * Returns: password
+ */
+
+#define PWDLEN 128 /* used by Linux */
+
+char *getpass (const char *prompt)
+{
+ char *s;
+ static char pwd[PWDLEN];
+ fputs (prompt,stdout);
+ fgets (pwd,PWDLEN-1,stdin);
+ if (s = strchr (pwd,'\n')) *s = '\0';
+ return pwd;
+}
diff --git a/imap/src/osdep/vms/os_vms.h b/imap/src/osdep/vms/os_vms.h
new file mode 100644
index 00000000..1da72f13
--- /dev/null
+++ b/imap/src/osdep/vms/os_vms.h
@@ -0,0 +1,52 @@
+/* ========================================================================
+ * Copyright 1988-2007 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- VMS version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 2 August 1994
+ * Last Edited: 30 January 2007
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unixio.h>
+#include <file.h>
+#include <stat.h>
+
+#define L_SET SEEK_SET
+#define L_INCR SEEK_CUR
+#define L_XTND SEEK_END
+
+#include "env_vms.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+
+#define gethostid clock
+#define random rand
+#define unlink delete
+
+char *getpass (const char *prompt);
+
+#define strtok_r(a,b,c) strtok(a,b)
diff --git a/imap/src/osdep/vms/pmatch.c b/imap/src/osdep/vms/pmatch.c
new file mode 100644
index 00000000..95a0bb86
--- /dev/null
+++ b/imap/src/osdep/vms/pmatch.c
@@ -0,0 +1,89 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: IMAP Wildcard Matching Routines (case-independent)
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 June 2000
+ * Last Edited: 30 August 2006
+ */
+
+/* Wildcard pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if pattern matches base, else NIL
+ */
+
+long pmatch_full (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ /* % at end, OK if no inferiors */
+ if (!pat[1]) return (delim && strchr (s,delim)) ? NIL : T;
+ /* scan remainder of string until delimiter */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while ((*s != delim) && *s++);
+ break;
+ case '*': /* match 0 or more characters */
+ if (!pat[1]) return T; /* * at end, unconditional match */
+ /* scan remainder of string */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while (*s++);
+ break;
+ case '\0': /* end of pattern */
+ return *s ? NIL : T; /* success if also end of base */
+ default: /* match this character */
+ return compare_uchar (*pat,*s) ? NIL : pmatch_full (s+1,pat+1,delim);
+ }
+ return NIL;
+}
+
+/* Directory pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if base is a matching directory of pattern, else NIL
+ */
+
+long dmatch (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ if (!*s) return T; /* end of base means have a subset match */
+ if (!*++pat) return NIL; /* % at end, no inferiors permitted */
+ /* scan remainder of string until delimiter */
+ do if (dmatch (s,pat,delim)) return T;
+ while ((*s != delim) && *s++);
+ if (*s && !s[1]) return T; /* ends with delimiter, must be subset */
+ return dmatch (s,pat,delim);/* do new scan */
+ case '*': /* match 0 or more characters */
+ return T; /* unconditional match */
+ case '\0': /* end of pattern */
+ break;
+ default: /* match this character */
+ if (*s) return compare_uchar (*pat,*s) ? NIL : dmatch (s+1,pat+1,delim);
+ /* end of base, return if at delimiter */
+ else if (*pat == delim) return T;
+ break;
+ }
+ return NIL;
+}
diff --git a/imap/src/osdep/vms/tcp_vms.h b/imap/src/osdep/vms/tcp_vms.h
new file mode 100644
index 00000000..ff747016
--- /dev/null
+++ b/imap/src/osdep/vms/tcp_vms.h
@@ -0,0 +1,52 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: VMS TCP/IP routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* TCP input buffer */
+
+#define BUFLEN 8192
+
+
+/* TCP I/O stream */
+
+#define TCPSTREAM struct tcp_stream
+TCPSTREAM {
+ char *host; /* host name */
+ unsigned long port; /* port number */
+ char *localhost; /* local host name */
+ int tcpsi; /* input socket */
+ int tcpso; /* output socket */
+ int ictr; /* input counter */
+ char *iptr; /* input pointer */
+ char ibuf[BUFLEN]; /* input buffer */
+};
+
+
+/* Local function prototypes */
+
+long tcp_abort (TCPSTREAM *stream);
diff --git a/imap/src/osdep/vms/tcp_vmsl.c b/imap/src/osdep/vms/tcp_vmsl.c
new file mode 100644
index 00000000..692c9212
--- /dev/null
+++ b/imap/src/osdep/vms/tcp_vmsl.c
@@ -0,0 +1,378 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: VMS TCP/IP routines for Netlib.
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 2 August 1994
+ * Last Edited: 13 January 2008
+ */
+
+/* Thanks to Yehavi Bourvine at The Hebrew University of Jerusalem who
+ contributed the original VMS code */
+
+#include <descrip.h>
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd);
+
+/* TCP/IP manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tcp_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+
+/* TCP/IP open
+ * Accepts: host name
+ * contact service name
+ * contact port number
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = NIL;
+ unsigned long sock;
+ int status;
+ char tmp[MAILTMPLEN];
+ /* hostname to connect to */
+ struct dsc$descriptor HostDesc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };
+ port &= 0xffff; /* erase flags */
+ /* assign a local socket */
+ if (!((status = net_assign (&sock)) & 0x1)) {
+ sprintf (tmp,"Unable to assign to net, status=%d",status);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ if (!((status = net_bind (&sock,1)) & 0x1)) {
+ sprintf (tmp,"Unable to create local socket, status=%d",status);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* open connection */
+ HostDesc.dsc$w_length = strlen (host);
+ HostDesc.dsc$a_pointer = host;
+ if (!((status = tcp_connect (&sock,&HostDesc,port)) & 0x1)) {
+ sprintf (tmp,"Can't connect to %.80s,%lu: %s",host,port,strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* create TCP/IP stream */
+ stream = (TCPSTREAM *) fs_get (sizeof (TCPSTREAM));
+ stream->host = cpystr (host); /* copy official host name */
+ /* copy local host name */
+ stream->localhost = cpystr (mylocalhost ());
+ stream->port = port; /* copy port number */
+ /* init sockets */
+ stream->tcpsi = stream->tcpso = sock;
+ stream->ictr = 0; /* init input counter */
+ return stream; /* return success */
+}
+
+/* TCP/IP authenticated open
+ * Accepts: NETMBX specifier
+ * service name
+ * returned user name buffer
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL;
+}
+
+/* TCP receive line
+ * Accepts: TCP stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *tcp_getline (TCPSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = tcp_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = tcp_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* TCP receive line or partial line
+ * Accepts: TCP stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!tcp_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!tcp_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* TCP/IP receive buffer
+ * Accepts: TCP/IP stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *buffer)
+{
+ unsigned long n;
+ char *bufptr = buffer;
+ while (size > 0) { /* until request satisfied */
+ if (!tcp_getdata (stream)) return NIL;
+ n = min (size,stream->ictr);/* number of bytes to transfer */
+ /* do the copy */
+ memcpy (bufptr,stream->iptr,n);
+ bufptr += n; /* update pointer */
+ stream->iptr +=n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -=n;
+ }
+ bufptr[0] = '\0'; /* tie off string */
+ return T;
+}
+
+
+/* TCP/IP receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getdata (TCPSTREAM *stream)
+{
+ char tmp[MAILTMPLEN];
+ int i,status;
+ /* Note: the doc says we need here dynamic descriptor, but we need static
+ * one... */
+ struct dsc$descriptor BufDesc = {BUFLEN,DSC$K_DTYPE_T,DSC$K_CLASS_S,
+ stream->ibuf};
+ static short iosb[4];
+ if (stream->tcpsi < 0) return NIL;
+ while (stream->ictr < 1) { /* if nothing in the buffer */
+ if (!((status = tcp_receive(&(stream->tcpsi), &BufDesc, iosb)) & 0x1)) {
+ sprintf (tmp,"Error reading from TcpIp/NETLIB, status=%d",status);
+ mm_log (tmp,ERROR);
+ return tcp_abort (stream);
+ }
+ if (iosb[1] > BUFLEN) i = BUFLEN;
+ else i = iosb[1];
+ if (i < 1) return tcp_abort (stream);
+ stream->ictr = i; /* set new byte count */
+ stream->iptr = stream->ibuf;/* point at TCP buffer */
+ }
+ return T;
+}
+
+/* TCP/IP send string as record
+ * Accepts: TCP/IP stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long tcp_soutr (TCPSTREAM *stream,char *string)
+{
+ return tcp_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* TCP/IP send string
+ * Accepts: TCP/IP stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
+{
+ int status;
+ struct dsc$descriptor_s BufDesc = {strlen(string),DSC$K_DTYPE_T,
+ DSC$K_CLASS_S,string };
+ /* 2 = Do not add \r\n */
+ return ((status = tcp_send (&(stream->tcpso),&BufDesc,2)) & 0x1) ? T :
+ tcp_abort (stream);
+}
+
+/* TCP/IP close
+ * Accepts: TCP/IP stream
+ */
+
+void tcp_close (TCPSTREAM *stream)
+{
+ tcp_abort (stream); /* nuke the stream */
+ /* flush host names */
+ fs_give ((void **) &stream->host);
+ fs_give ((void **) &stream->localhost);
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* TCP/IP abort stream
+ * Accepts: TCP/IP stream
+ * Returns: NIL always
+ */
+
+long tcp_abort (TCPSTREAM *stream)
+{
+ if (stream->tcpsi >= 0) { /* no-op if no socket */
+ /* nuke the socket */
+ tcp_disconnect (&(stream->tcpsi));
+ stream->tcpsi = stream->tcpso = -1;
+ }
+ return NIL;
+}
+
+/* TCP/IP get host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_host (TCPSTREAM *stream)
+{
+ return stream->host; /* return host name */
+}
+
+
+/* TCP/IP get remote host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_remotehost (TCPSTREAM *stream)
+{
+ return stream->host; /* return host name */
+}
+
+
+/* TCP/IP return port for this stream
+ * Accepts: TCP/IP stream
+ * Returns: port number for this stream
+ */
+
+unsigned long tcp_port (TCPSTREAM *stream)
+{
+ return stream->port; /* return port number */
+}
+
+
+/* TCP/IP get local host name
+ * Accepts: TCP/IP stream
+ * Returns: local host name
+ */
+
+char *tcp_localhost (TCPSTREAM *stream)
+{
+ return stream->localhost; /* return local host name */
+}
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost ()
+{
+ int status;
+ char tmp[MAILTMPLEN];
+ if (!myLocalHost) { /* have local host yet? */
+ /* receives local host name */
+ struct dsc$descriptor LocalhostDesc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_D,NULL};
+ if (!((status = net_get_hostname (&LocalhostDesc)) & 0x1)) {
+ sprintf (tmp,"Can't get local hostname, status=%d",status);
+ mm_log (tmp,ERROR);
+ return "UNKNOWN";
+ }
+ strncpy (tmp,LocalhostDesc.dsc$a_pointer,LocalhostDesc.dsc$w_length);
+ tmp[LocalhostDesc.dsc$w_length] = '\0';
+ str$free1_dx (&LocalhostDesc);
+ myLocalHost = cpystr (tmp);
+ }
+ return myLocalHost;
+}
+
+/* TCP/IP return canonical form of host name
+ * Accepts: host name
+ * Returns: canonical form of host name
+ */
+
+char *tcp_canonical (char *name)
+{
+ return name;
+}
+
+
+/* TCP/IP get client host name (server calls only)
+ * Returns: client host name
+ */
+
+char *tcp_clienthost ()
+{
+ return "UNKNOWN";
+}
diff --git a/imap/src/osdep/vms/tcp_vmsm.c b/imap/src/osdep/vms/tcp_vmsm.c
new file mode 100644
index 00000000..bc9d9ca7
--- /dev/null
+++ b/imap/src/osdep/vms/tcp_vmsm.c
@@ -0,0 +1,479 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: VMS TCP/IP routines for Multinet
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 2 August 1994
+ * Last Edited: 13 January 2008
+ */
+
+
+static tcptimeout_t tmoh = NIL; /* TCP timeout handler routine */
+static long ttmo_read = 0; /* TCP timeouts, in seconds */
+static long ttmo_write = 0;
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd);
+
+/* TCP/IP manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tcp_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_TIMEOUT:
+ tmoh = (tcptimeout_t) value;
+ case GET_TIMEOUT:
+ ret = (void *) tmoh;
+ break;
+ case SET_READTIMEOUT:
+ ttmo_read = (long) value;
+ case GET_READTIMEOUT:
+ ret = (void *) ttmo_read;
+ break;
+ case SET_WRITETIMEOUT:
+ ttmo_write = (long) value;
+ case GET_WRITETIMEOUT:
+ ret = (void *) ttmo_write;
+ break;
+ }
+ return ret;
+}
+
+/* TCP/IP open
+ * Accepts: host name
+ * contact service name
+ * contact port number
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = NIL;
+ int sock;
+ char *s;
+ struct sockaddr_in sin;
+ struct hostent *host_name;
+ char hostname[MAILTMPLEN];
+ char tmp[MAILTMPLEN];
+ struct protoent *pt = getprotobyname ("tcp");
+ struct servent *sv = NIL;
+ port &= 0xffff; /* erase flags */
+ if (service) { /* service specified? */
+ if (*service == '*') { /* yes, special alt driver kludge? */
+ sv = getservbyname (service + 1,"tcp");
+ }
+ else sv = getservbyname (service,"tcp");
+ }
+ /* user service name port */
+ if (sv) port = ntohs (sin.sin_port = sv->s_port);
+ /* copy port number in network format */
+ else sin.sin_port = htons (port);
+ /* The domain literal form is used (rather than simply the dotted decimal
+ as with other Unix programs) because it has to be a valid "host name"
+ in mailsystem terminology. */
+ /* look like domain literal? */
+ if (host[0] == '[' && host[(strlen (host))-1] == ']') {
+ strcpy (hostname,host+1); /* yes, copy number part */
+ hostname[(strlen (hostname))-1] = '\0';
+ if ((sin.sin_addr.s_addr = inet_addr (hostname)) != -1) {
+ sin.sin_family = AF_INET; /* family is always Internet */
+ strcpy (hostname,host); /* hostname is user's argument */
+ }
+ else {
+ sprintf (tmp,"Bad format domain-literal: %.80s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ }
+
+ else { /* lookup host name, note that brain-dead Unix
+ requires lowercase! */
+ strcpy (hostname,host); /* in case host is in write-protected memory */
+ if ((host_name = gethostbyname (lcase (hostname)))) {
+ /* copy address type */
+ sin.sin_family = host_name->h_addrtype;
+ /* copy host name */
+ strcpy (hostname,host_name->h_name);
+ /* copy host addresses */
+ memcpy (&sin.sin_addr,host_name->h_addr,host_name->h_length);
+ }
+ else {
+ sprintf (tmp,"No such host as %.80s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ }
+ /* get a TCP stream */
+ if ((sock = socket (sin.sin_family,SOCK_STREAM,pt ? pt->p_proto : 0)) < 0) {
+ sprintf (tmp,"Unable to create TCP socket: %s",strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+#if 0
+ /* Maybe this test is necessary. It depends upon how VMS implements
+ * fd_set. UNIX-style fd_set needs it; Windows-style does not.
+ */
+ else if (sock >= FD_SETSIZE) {/* unselectable sockets are useless */
+ sprintf (tmp,"Unable to create selectable TCP socket (%d >= %d)",
+ sock,FD_SETSIZE);
+ close (sock);
+ return NIL;
+ }
+#endif
+ /* open connection */
+ if (connect (sock,(struct sockaddr *)&sin,sizeof (sin)) < 0) {
+ sprintf (tmp,"Can't connect to %.80s,%d: %s",hostname,port,
+ strerror (errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ /* create TCP/IP stream */
+ stream = (TCPSTREAM *) fs_get (sizeof (TCPSTREAM));
+ /* copy official host name */
+ stream->host = cpystr (hostname);
+ /* get local name */
+ gethostname (tmp,MAILTMPLEN-1);
+ stream->localhost = cpystr ((host_name = gethostbyname (tmp)) ?
+ host_name->h_name : tmp);
+ /* init sockets */
+ stream->port = port; /* port number */
+ stream->tcpsi = stream->tcpso = sock;
+ stream->ictr = 0; /* init input counter */
+ return stream; /* return success */
+}
+
+/* TCP/IP authenticated open
+ * Accepts: NETMBX specifier
+ * service name
+ * returned user name buffer
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL;
+}
+
+/* TCP receive line
+ * Accepts: TCP stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *tcp_getline (TCPSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = tcp_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = tcp_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* TCP receive line or partial line
+ * Accepts: TCP stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!tcp_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!tcp_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* TCP/IP receive buffer
+ * Accepts: TCP/IP stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *buffer)
+{
+ unsigned long n;
+ char *bufptr = buffer;
+ while (size > 0) { /* until request satisfied */
+ if (!tcp_getdata (stream)) return NIL;
+ n = min (size,stream->ictr);/* number of bytes to transfer */
+ /* do the copy */
+ memcpy (bufptr,stream->iptr,n);
+ bufptr += n; /* update pointer */
+ stream->iptr +=n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -=n;
+ }
+ bufptr[0] = '\0'; /* tie off string */
+ return T;
+}
+
+/* TCP/IP receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getdata (TCPSTREAM *stream)
+{
+ int i;
+ fd_set fds,efds;
+ struct timeval tmo;
+ time_t t = time (0);
+ if (stream->tcpsi < 0) return NIL;
+ while (stream->ictr < 1) { /* if nothing in the buffer */
+ time_t tl = time (0); /* start of request */
+ tmo.tv_sec = ttmo_read; /* read timeout */
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_ZERO (&efds); /* handle errors too */
+ FD_SET (stream->tcpsi,&fds);/* set bit in selection vector */
+ FD_SET(stream->tcpsi,&efds);/* set bit in error selection vector */
+ errno = NIL; /* block and read */
+ while (((i = select (getdtablesize (),&fds,0,&efds,ttmo_read ? &tmo:0))<0)
+ && (errno == EINTR));
+ if (!i) { /* timeout? */
+ time_t tc = time (0);
+ if (tmoh && ((*tmoh) (tc - t,tc - tl))) continue;
+ else return tcp_abort (stream);
+ }
+ else if (i < 0) return tcp_abort (stream);
+ while (((i = socket_read (stream->tcpsi,stream->ibuf,BUFLEN)) < 0) &&
+ (errno == EINTR));
+ if (i < 1) return tcp_abort (stream);
+ stream->iptr = stream->ibuf;/* point at TCP buffer */
+ stream->ictr = i; /* set new byte count */
+ }
+ return T;
+}
+
+/* TCP/IP send string as record
+ * Accepts: TCP/IP stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long tcp_soutr (TCPSTREAM *stream,char *string)
+{
+ return tcp_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* TCP/IP send string
+ * Accepts: TCP/IP stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
+{
+ int i;
+ fd_set fds;
+ struct timeval tmo;
+ time_t t = time (0);
+ if (stream->tcpso < 0) return NIL;
+ while (size > 0) { /* until request satisfied */
+ time_t tl = time (0); /* start of request */
+ tmo.tv_sec = ttmo_write; /* write timeout */
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_SET (stream->tcpso,&fds);/* set bit in selection vector */
+ errno = NIL; /* block and write */
+ while (((i = select (getdtablesize (),0,&fds,0,ttmo_write ? &tmo : 0)) < 0)
+ && (errno == EINTR));
+ if (!i) { /* timeout? */
+ time_t tc = time (0);
+ if (tmoh && ((*tmoh) (tc - t,tc - tl))) continue;
+ else return tcp_abort (stream);
+ }
+ else if (i < 0) return tcp_abort (stream);
+ while (((i = socket_write (stream->tcpso,string,size)) < 0) &&
+ (errno == EINTR));
+ if (i < 0) return tcp_abort (stream);
+ size -= i; /* how much we sent */
+ string += i;
+ }
+ return T; /* all done */
+}
+
+/* TCP/IP close
+ * Accepts: TCP/IP stream
+ */
+
+void tcp_close (TCPSTREAM *stream)
+{
+ tcp_abort (stream); /* nuke the stream */
+ /* flush host names */
+ fs_give ((void **) &stream->host);
+ fs_give ((void **) &stream->localhost);
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* TCP/IP abort stream
+ * Accepts: TCP/IP stream
+ * Returns: NIL always
+ */
+
+long tcp_abort (TCPSTREAM *stream)
+{
+ int i;
+ if (stream->tcpsi >= 0) { /* no-op if no socket */
+ /* nuke the socket */
+ socket_close (stream->tcpsi);
+ if (stream->tcpsi != stream->tcpso) socket_close (stream->tcpso);
+ stream->tcpsi = stream->tcpso = -1;
+ }
+ return NIL;
+}
+
+/* TCP/IP get host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_host (TCPSTREAM *stream)
+{
+ return stream->host; /* return host name */
+}
+
+
+/* TCP/IP get remote host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_remotehost (TCPSTREAM *stream)
+{
+ return stream->host; /* return host name */
+}
+
+
+/* TCP/IP return port for this stream
+ * Accepts: TCP/IP stream
+ * Returns: port number for this stream
+ */
+
+unsigned long tcp_port (TCPSTREAM *stream)
+{
+ return stream->port; /* return port number */
+}
+
+
+/* TCP/IP get local host name
+ * Accepts: TCP/IP stream
+ * Returns: local host name
+ */
+
+char *tcp_localhost (TCPSTREAM *stream)
+{
+ return stream->localhost; /* return local host name */
+}
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost ()
+{
+ char tmp[MAILTMPLEN];
+ struct hostent *hn;
+ if (!myLocalHost) { /* have local host yet? */
+ gethostname(tmp,MAILTMPLEN);/* get local host name */
+ myLocalHost = cpystr ((hn = gethostbyname (tmp)) ? hn->h_name : tmp);
+ }
+ return myLocalHost;
+}
+
+
+/* TCP/IP return canonical form of host name
+ * Accepts: host name
+ * Returns: canonical form of host name
+ */
+
+char *tcp_canonical (char *name)
+{
+ char host[MAILTMPLEN];
+ struct hostent *he;
+ /* look like domain literal? */
+ if (name[0] == '[' && name[strlen (name) - 1] == ']') return name;
+ /* note that Unix requires lowercase! */
+ else return (he = gethostbyname (lcase (strcpy (host,name)))) ?
+ he->h_name : name;
+}
+
+
+/* TCP/IP get client host name (server calls only)
+ * Returns: client host name
+ */
+
+char *tcp_clienthost ()
+{
+ return "UNKNOWN";
+}
diff --git a/imap/src/osdep/vms/tcp_vmsn.c b/imap/src/osdep/vms/tcp_vmsn.c
new file mode 100644
index 00000000..4648e48d
--- /dev/null
+++ b/imap/src/osdep/vms/tcp_vmsn.c
@@ -0,0 +1,222 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy VMS TCP/IP routines for non-TCP/IP systems
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 2 August 1994
+ * Last Edited: 30 August 2006
+ */
+
+/* TCP/IP manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tcp_parameters (long function,void *value)
+{
+ return NIL;
+}
+
+
+/* TCP/IP open
+ * Accepts: host name
+ * contact service name
+ * contact port number
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_open (char *host,char *service,unsigned long port)
+{
+ char tmp[MAILTMPLEN];
+ port &= 0xffff; /* erase flags */
+ if (port) sprintf (tmp,"Can't connect to %.80s,%d: no TCP",host,port);
+ else sprintf (tmp,"Can't connect to %.80s,%s: no TCP",host,service);
+ mm_log (tmp,ERROR);
+ return NIL;
+}
+
+
+/* TCP/IP authenticated open
+ * Accepts: NETMBX specifier
+ * service name
+ * returned user name buffer
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL;
+}
+
+/* TCP/IP receive line
+ * Accepts: TCP/IP stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *tcp_getline (TCPSTREAM *stream)
+{
+ return NIL;
+}
+
+
+/* TCP/IP receive buffer
+ * Accepts: TCP/IP stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *buffer)
+{
+ return NIL;
+}
+
+
+/* TCP/IP receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getdata (TCPSTREAM *stream)
+{
+ return NIL;
+}
+
+/* TCP/IP send string as record
+ * Accepts: TCP/IP stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long tcp_soutr (TCPSTREAM *stream,char *string)
+{
+ return NIL;
+}
+
+
+/* TCP/IP send string
+ * Accepts: TCP/IP stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
+{
+ return NIL;
+}
+
+
+/* TCP/IP close
+ * Accepts: TCP/IP stream
+ */
+
+void tcp_close (TCPSTREAM *stream)
+{
+}
+
+
+/* TCP/IP abort stream
+ * Accepts: TCP/IP stream
+ * Returns: NIL always
+ */
+
+long tcp_abort (TCPSTREAM *stream)
+{
+ return NIL;
+}
+
+/* TCP/IP get host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_host (TCPSTREAM *stream)
+{
+ return NIL;
+}
+
+
+/* TCP/IP get remote host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_remotehost (TCPSTREAM *stream)
+{
+ return NIL;
+}
+
+
+/* TCP/IP get local host name
+ * Accepts: TCP/IP stream
+ * Returns: local host name
+ */
+
+char *tcp_localhost (TCPSTREAM *stream)
+{
+ return NIL;
+}
+
+
+/* TCP/IP return port for this stream
+ * Accepts: TCP/IP stream
+ * Returns: port number for this stream
+ */
+
+unsigned long tcp_port (TCPSTREAM *stream)
+{
+ return 0xffffffff; /* return port number */
+}
+
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost ()
+{
+ /* have local host yet? */
+ if (!myLocalHost) myLocalHost = cpystr (getenv ("SYS$NODE"));
+ return myLocalHost;
+}
+
+/* TCP/IP return canonical form of host name
+ * Accepts: host name
+ * Returns: canonical form of host name
+ */
+
+char *tcp_canonical (char *name)
+{
+ return name;
+}
+
+
+/* TCP/IP get client host name (server calls only)
+ * Returns: client host name
+ */
+
+char *tcp_clienthost ()
+{
+ return "UNKNOWN";
+}
diff --git a/imap/src/osdep/wce/drivers.bat b/imap/src/osdep/wce/drivers.bat
new file mode 100644
index 00000000..0964f537
--- /dev/null
+++ b/imap/src/osdep/wce/drivers.bat
@@ -0,0 +1,33 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Driver Linkage Generator for DOS/NT
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 11 October 1989
+REM Last Edited:30 August 2006
+
+REM Erase old driver linkage
+IF EXIST LINKAGE.* DEL LINKAGE.*
+
+REM Now define the new list
+FOR %%D IN (%1 %2 %3 %4 %5 %6 %7 %8 %9) DO CALL DRIVRAUX %%D
+
+EXIT 0
diff --git a/imap/src/osdep/wce/drivraux.bat b/imap/src/osdep/wce/drivraux.bat
new file mode 100644
index 00000000..30649a78
--- /dev/null
+++ b/imap/src/osdep/wce/drivraux.bat
@@ -0,0 +1,30 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Driver Linkage Generator auxillary for NT/Win9x
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 11 October 1989
+REM Last Edited:30 August 2006
+
+ECHO extern DRIVER %1driver; >> LINKAGE.H
+REM Note the introduction of the caret to quote the ampersand in NT
+if "%OS%" == "Windows_NT" ECHO mail_link (^&%1driver); /* link in the %1 driver */ >> LINKAGE.C
+if "%OS%" == "" ECHO mail_link (&%1driver); /* link in the %1 driver */ >> LINKAGE.C
diff --git a/imap/src/osdep/wce/dummy.h b/imap/src/osdep/wce/dummy.h
new file mode 100644
index 00000000..32650e06
--- /dev/null
+++ b/imap/src/osdep/wce/dummy.h
@@ -0,0 +1,43 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 9 May 1991
+ * Last Edited: 30 August 2006
+ */
+
+/* Exported function prototypes */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat);
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat);
+long scan_contents (DRIVER *dtb,char *name,char *contents,
+ unsigned long csiz,unsigned long fsiz);
+long dummy_scan_contents (char *name,char *contents,unsigned long csiz,
+ unsigned long fsiz);
+long dummy_create (MAILSTREAM *stream,char *mailbox);
+long dummy_create_path (MAILSTREAM *stream,char *path,long dirmode);
+long dummy_delete (MAILSTREAM *stream,char *mailbox);
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname);
+char *dummy_file (char *dst,char *name);
+long dummy_canonicalize (char *tmp,char *ref,char *pat);
diff --git a/imap/src/osdep/wce/dummywce.c b/imap/src/osdep/wce/dummywce.c
new file mode 100644
index 00000000..2440be27
--- /dev/null
+++ b/imap/src/osdep/wce/dummywce.c
@@ -0,0 +1,301 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Dummy routines for WCE
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 24 May 1993
+ * Last Edited: 30 August 2006
+ */
+
+
+#include <ctype.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <direct.h>
+#include "mail.h"
+#include "osdep.h"
+#include <sys\stat.h>
+#include <dos.h>
+#include "dummy.h"
+#include "misc.h"
+
+/* Function prototypes */
+
+DRIVER *dummy_valid (char *name);
+void *dummy_parameters (long function,void *value);
+MAILSTREAM *dummy_open (MAILSTREAM *stream);
+void dummy_close (MAILSTREAM *stream,long options);
+long dummy_ping (MAILSTREAM *stream);
+void dummy_check (MAILSTREAM *stream);
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options);
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+
+/* Dummy routines */
+
+
+/* Driver dispatch used by MAIL */
+
+DRIVER dummydriver = {
+ "dummy", /* driver name */
+ DR_LOCAL|DR_MAIL, /* driver flags */
+ (DRIVER *) NIL, /* next driver */
+ dummy_valid, /* mailbox is valid for us */
+ dummy_parameters, /* manipulate parameters */
+ dummy_scan, /* scan mailboxes */
+ dummy_list, /* list mailboxes */
+ dummy_lsub, /* list subscribed mailboxes */
+ NIL, /* subscribe to mailbox */
+ NIL, /* unsubscribe from mailbox */
+ dummy_create, /* create mailbox */
+ dummy_delete, /* delete mailbox */
+ dummy_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ dummy_open, /* open mailbox */
+ dummy_close, /* close mailbox */
+ NIL, /* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message structure */
+ NIL, /* fetch header */
+ NIL, /* fetch text */
+ NIL, /* fetch message data */
+ NIL, /* unique identifier */
+ NIL, /* message number from UID */
+ NIL, /* modify flags */
+ NIL, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ dummy_ping, /* ping mailbox to see if still alive */
+ dummy_check, /* check for new messages */
+ dummy_expunge, /* expunge deleted messages */
+ dummy_copy, /* copy messages to another mailbox */
+ dummy_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+
+ /* prototype stream */
+MAILSTREAM dummyproto = {&dummydriver};
+
+ /* driver parameters */
+static char *file_extension = NIL;
+
+/* Dummy validate mailbox
+ * Accepts: mailbox name
+ * Returns: our driver if name is valid, NIL otherwise
+ */
+
+DRIVER *dummy_valid (char *name)
+{
+ char *s,tmp[MAILTMPLEN];
+ struct stat sbuf;
+ /* must be valid local mailbox */
+ return (name && *name && (*name != '{') &&
+ (s = mailboxfile (tmp,name)) && (!*s || !stat (s,&sbuf))) ?
+ &dummydriver : NIL;
+}
+
+
+/* Dummy manipulate driver parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *dummy_parameters (long function,void *value)
+{
+ return value;
+}
+
+/* Dummy scan mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ * string to scan
+ */
+
+void dummy_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
+{
+ /* return silently */
+}
+
+/* Dummy list mailboxes
+ * Accepts: mail stream
+ * reference
+ * pattern to search
+ */
+
+void dummy_list (MAILSTREAM *stream,char *ref,char *pat)
+{
+ /* return silently */
+}
+
+
+/* Dummy list subscribed mailboxes
+ * Accepts: mail stream
+ * pattern to search
+ */
+
+void dummy_lsub (MAILSTREAM *stream,char *ref,char *pat)
+{
+ /* return silently */
+}
+
+/* Dummy create mailbox
+ * Accepts: mail stream
+ * mailbox name to create
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_create (MAILSTREAM *stream,char *mailbox)
+{
+ return NIL; /* always fails */
+}
+
+
+/* Dummy delete mailbox
+ * Accepts: mail stream
+ * mailbox name to delete
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_delete (MAILSTREAM *stream,char *mailbox)
+{
+ return NIL; /* always fails */
+}
+
+
+/* Mail rename mailbox
+ * Accepts: mail stream
+ * old mailbox name
+ * new mailbox name
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_rename (MAILSTREAM *stream,char *old,char *newname)
+{
+ return NIL; /* always fails */
+}
+
+/* Dummy open
+ * Accepts: stream to open
+ * Returns: stream on success, NIL on failure
+ */
+
+MAILSTREAM *dummy_open (MAILSTREAM *stream)
+{
+ char tmp[MAILTMPLEN];
+ /* OP_PROTOTYPE call or silence */
+ if (!stream || stream->silent) return NIL;
+ if (compare_cstring (stream->mailbox,"INBOX")) {
+ sprintf (tmp,"Not a mailbox: %s",stream->mailbox);
+ mm_log (tmp,ERROR);
+ return NIL; /* always fails */
+ }
+ if (!stream->silent) { /* only if silence not requested */
+ mail_exists (stream,0); /* say there are 0 messages */
+ mail_recent (stream,0);
+ stream->uid_validity = time (0);
+ }
+ stream->inbox = T; /* note that it's an INBOX */
+ return stream; /* return success */
+}
+
+
+/* Dummy close
+ * Accepts: MAIL stream
+ * options
+ */
+
+void dummy_close (MAILSTREAM *stream,long options)
+{
+ /* return silently */
+}
+
+/* Dummy ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+long dummy_ping (MAILSTREAM *stream)
+{
+ return T;
+}
+
+
+/* Dummy check mailbox
+ * Accepts: MAIL stream
+ * No-op for readonly files, since read/writer can expunge it from under us!
+ */
+
+void dummy_check (MAILSTREAM *stream)
+{
+ dummy_ping (stream); /* invoke ping */
+}
+
+
+/* Dummy expunge mailbox
+ * Accepts: MAIL stream
+ * sequence to expunge if non-NIL
+ * expunge options
+ * Returns: T, always
+ */
+
+long dummy_expunge (MAILSTREAM *stream,char *sequence,long options)
+{
+ return LONGT;
+}
+
+/* Dummy copy message(s)
+ * Accepts: MAIL stream
+ * sequence
+ * destination mailbox
+ * options
+ * Returns: T if copy successful, else NIL
+ */
+
+long dummy_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
+{
+ if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
+ mail_sequence (stream,sequence)) fatal ("Impossible dummy_copy");
+ return NIL;
+}
+
+
+/* Dummy append message string
+ * Accepts: mail stream
+ * destination mailbox
+ * stringstruct of message to append
+ * Returns: T on success, NIL on failure
+ */
+
+long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
+{
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"Can't append to %s",mailbox);
+ mm_log (tmp,ERROR); /* pass up error */
+ return NIL; /* always fails */
+}
diff --git a/imap/src/osdep/wce/env_wce.c b/imap/src/osdep/wce/env_wce.c
new file mode 100644
index 00000000..ea2dbd98
--- /dev/null
+++ b/imap/src/osdep/wce/env_wce.c
@@ -0,0 +1,301 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: WCE environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+static char *myUserName = NIL; /* user name */
+static char *myLocalHost = NIL; /* local host name */
+static char *myClientHost = NIL;/* client host name */
+static char *myServerHost = NIL;/* server host name */
+static char *myHomeDir = NIL; /* home directory name */
+static char *myNewsrc = NIL; /* newsrc file name */
+static char *sysInbox = NIL; /* system inbox name */
+static long list_max_level = 5; /* maximum level of list recursion */
+static short no822tztext = NIL; /* disable RFC [2]822 timezone text */
+ /* home namespace */
+static NAMESPACE nshome = {"",'\\',NIL,NIL};
+ /* namespace list */
+static NAMESPACE *nslist[3] = {&nshome,NIL,NIL};
+static long alarm_countdown = 0;/* alarm count down */
+static void (*alarm_rang) (); /* alarm interrupt function */
+static unsigned int rndm = 0; /* initial `random' number */
+
+
+/* Dummy definitions to prevent errors */
+
+#define server_login(user,pass,authuser,argc,argv) NIL
+#define authserver_login(user,authuser,argc,argv) NIL
+#define myusername() ""
+#define MD5ENABLE "\\.nosuch.."
+
+#include "pmatch.c" /* include wildcard pattern matcher */
+
+/* Environment manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *env_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case GET_NAMESPACE:
+ ret = (void *) nslist;
+ break;
+ case SET_HOMEDIR:
+ myHomeDir = cpystr ((char *) value);
+ case GET_HOMEDIR:
+ ret = (void *) myHomeDir;
+ break;
+ case SET_LOCALHOST:
+ myLocalHost = cpystr ((char *) value);
+ case GET_LOCALHOST:
+ ret = (void *) myLocalHost;
+ break;
+ case SET_NEWSRC:
+ if (myNewsrc) fs_give ((void **) &myNewsrc);
+ myNewsrc = cpystr ((char *) value);
+ case GET_NEWSRC:
+ if (!myNewsrc) { /* set news file name if not defined */
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"%s\\NEWSRC",myhomedir ());
+ myNewsrc = cpystr (tmp);
+ }
+ ret = (void *) myNewsrc;
+ break;
+ case SET_SYSINBOX:
+ if (sysInbox) fs_give ((void **) &sysInbox);
+ sysInbox = cpystr ((char *) value);
+ case GET_SYSINBOX:
+ ret = (void *) sysInbox;
+ break;
+ case SET_LISTMAXLEVEL:
+ list_max_level = (long) value;
+ case GET_LISTMAXLEVEL:
+ ret = (void *) list_max_level;
+ break;
+ case SET_DISABLE822TZTEXT:
+ no822tztext = value ? T : NIL;
+ case GET_DISABLE822TZTEXT:
+ ret = (void *) (no822tztext ? VOIDT : NIL);
+ break;
+ }
+ return ret;
+}
+
+/* Write current time
+ * Accepts: destination string
+ * optional format of day-of-week prefix
+ * format of date and time
+ * flag whether to append symbolic timezone
+ */
+
+static void do_date (char *date,char *prefix,char *fmt,int suffix)
+{
+ time_t tn = time (0);
+ struct tm *t = gmtime (&tn);
+ int zone = t->tm_hour * 60 + t->tm_min;
+ int julian = t->tm_yday;
+ t = localtime (&tn); /* get local time now */
+ /* minus UTC minutes since midnight */
+ zone = t->tm_hour * 60 + t->tm_min - zone;
+ /* julian can be one of:
+ * 36x local time is December 31, UTC is January 1, offset -24 hours
+ * 1 local time is 1 day ahead of UTC, offset +24 hours
+ * 0 local time is same day as UTC, no offset
+ * -1 local time is 1 day behind UTC, offset -24 hours
+ * -36x local time is January 1, UTC is December 31, offset +24 hours
+ */
+ if (julian = t->tm_yday -julian)
+ zone += ((julian < 0) == (abs (julian) == 1)) ? -24*60 : 24*60;
+ if (prefix) { /* want day of week? */
+ sprintf (date,prefix,days[t->tm_wday]);
+ date += strlen (date); /* make next sprintf append */
+ }
+ /* output the date */
+ sprintf (date,fmt,t->tm_mday,months[t->tm_mon],t->tm_year+1900,
+ t->tm_hour,t->tm_min,t->tm_sec,zone/60,abs (zone) % 60);
+ if (suffix) { /* append timezone suffix if desired */
+ char *tz;
+ tzset (); /* get timezone from TZ environment stuff */
+ tz = tzname[daylight ? (((struct tm *) t)->tm_isdst > 0) : 0];
+ if (tz && tz[0]) sprintf (date + strlen (date)," (%s)",tz);
+ }
+}
+
+
+/* Write current time in RFC 822 format
+ * Accepts: destination string
+ */
+
+void rfc822_date (char *date)
+{
+ do_date (date,"%s, ","%d %s %d %02d:%02d:%02d %+03d%02d",
+ no822tztext ? NIL : T);
+}
+
+
+/* Write current time in internal format
+ * Accepts: destination string
+ */
+
+void internal_date (char *date)
+{
+ do_date (date,NIL,"%02d-%s-%d %02d:%02d:%02d %+03d%02d",NIL);
+}
+
+/* Return random number
+ */
+
+long random ()
+{
+ if (!rndm) srand (rndm = (unsigned) time (0L));
+ return (long) rand ();
+}
+
+/* Return default drive
+ * Returns: default drive
+ */
+
+static char *defaultDrive (void)
+{
+ char *s;
+ return ((s = getenv ("SystemDrive")) && *s) ? s : "C:";
+}
+
+
+/* Return home drive from environment variables
+ * Returns: home drive
+ */
+
+static char *homeDrive (void)
+{
+ char *s;
+ return ((s = getenv ("HOMEDRIVE")) && *s) ? s : defaultDrive ();
+}
+
+
+/* Return home path from environment variables
+ * Accepts: path to write into
+ * Returns: home path or NIL if it can't be determined
+ */
+
+static char *homePath (char *path)
+{
+ int i;
+ char *s;
+ if (!((s = getenv ("HOMEPATH")) && (i = strlen (s)))) return NIL;
+ if (((s[i-1] == '\\') || (s[i-1] == '/'))) s[i-1] = '\0';
+ sprintf (path,"%s%s",homeDrive (),s);
+ return path;
+}
+
+/* Return my home directory name
+ * Returns: my home directory name
+ */
+
+char *myhomedir ()
+{
+ char tmp[MAILTMPLEN];
+ /* initialize if first time */
+ if (!myHomeDir) myHomeDir = homePath (tmp);
+ return myHomeDir ? myHomeDir : homeDrive ();
+}
+
+/* Return system standard INBOX
+ * Accepts: buffer string
+ */
+
+char *sysinbox ()
+{
+ char tmp[MAILTMPLEN];
+ if (!sysInbox) { /* initialize if first time */
+ sprintf (tmp,"%s\\INBOX",myhomedir ());
+ sysInbox = cpystr (tmp); /* system inbox is from mail spool */
+ }
+ return sysInbox;
+}
+
+
+/* Return mailbox file name
+ * Accepts: destination buffer
+ * mailbox name
+ * Returns: file name
+ */
+
+char *mailboxfile (char *dst,char *name)
+{
+ char *dir = myhomedir ();
+ *dst = '\0'; /* default to empty string */
+ if (((name[0] == 'I') || (name[0] == 'i')) &&
+ ((name[1] == 'N') || (name[1] == 'n')) &&
+ ((name[2] == 'B') || (name[2] == 'b')) &&
+ ((name[3] == 'O') || (name[3] == 'o')) &&
+ ((name[4] == 'X') || (name[4] == 'x')) && !name[5]) name = NIL;
+ /* reject namespace names or names with / */
+ if (name && ((*name == '#') || strchr (name,'/'))) return NIL;
+ else if (!name) return dst; /* driver selects the INBOX name */
+ /* absolute path name? */
+ else if ((*name == '\\') || (name[1] == ':')) return strcpy (dst,name);
+ /* build resulting name */
+ sprintf (dst,"%s\\%s",dir,name);
+ return dst; /* return it */
+}
+
+
+/* Determine default prototype stream to user
+ * Accepts: type (NIL for create, T for append)
+ * Returns: default prototype stream
+ */
+
+MAILSTREAM *default_proto (long type)
+{
+ extern MAILSTREAM CREATEPROTO,APPENDPROTO;
+ return type ? &APPENDPROTO : &CREATEPROTO;
+}
+
+/* Emulator for BSD syslog() routine
+ * Accepts: priority
+ * message
+ * parameters
+ */
+
+void syslog (int priority,const char *message,...)
+{
+}
+
+
+/* Emulator for BSD openlog() routine
+ * Accepts: identity
+ * options
+ * facility
+ */
+
+void openlog (const char *ident,int logopt,int facility)
+{
+}
diff --git a/imap/src/osdep/wce/env_wce.h b/imap/src/osdep/wce/env_wce.h
new file mode 100644
index 00000000..e3c6c7f0
--- /dev/null
+++ b/imap/src/osdep/wce/env_wce.h
@@ -0,0 +1,70 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: WCE environment routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+#define SUBSCRIPTIONFILE(t) sprintf (t,"%s\\MAILBOX.LST",myhomedir ())
+#define SUBSCRIPTIONTEMP(t) sprintf (t,"%s\\MAILBOX.TMP",myhomedir ())
+
+#define L_SET SEEK_SET
+
+/* Function prototypes */
+
+#include "env.h"
+
+static char *defaultDrive (void);
+static char *homeDrive (void);
+static char *homePath (char *path);
+char *sysinbox ();
+long random ();
+unsigned long unix_crlfcpy (char **dst,unsigned long *dstl,char *src,
+ unsigned long srcl);
+unsigned long unix_crlflen (STRING *s);
+#define getpid random
+
+
+/* syslog() emulation */
+
+#define LOG_MAIL (2<<3) /* mail system */
+#define LOG_DAEMON (3<<3) /* system daemons */
+#define LOG_AUTH (4<<3) /* security/authorization messages */
+#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 signification condition */
+#define LOG_INFO 6 /* informational */
+#define LOG_DEBUG 7 /* debug-level messages */
+#define LOG_PID 0x01 /* log the pid with each message */
+#define LOG_CONS 0x02 /* log on the console if errors in sending */
+#define LOG_ODELAY 0x04 /* delay open until syslog() is called */
+#define LOG_NDELAY 0x08 /* don't delay open */
+#define LOG_NOWAIT 0x10 /* if forking to log on console, don't wait() */
+
+void openlog (const char *ident,int logopt,int facility);
+void syslog (int priority,const char *message,...);
diff --git a/imap/src/osdep/wce/fs_wce.c b/imap/src/osdep/wce/fs_wce.c
new file mode 100644
index 00000000..03908328
--- /dev/null
+++ b/imap/src/osdep/wce/fs_wce.c
@@ -0,0 +1,62 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Free storage management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Get a block of free storage
+ * Accepts: size of desired block
+ * Returns: free storage block
+ */
+
+void *fs_get (size_t size)
+{
+ void *block = malloc (size ? size : (size_t) 1);
+ if (!block) fatal ("Out of memory");
+ return (block);
+}
+
+
+/* Resize a block of free storage
+ * Accepts: ** pointer to current block
+ * new size
+ */
+
+void fs_resize (void **block,size_t size)
+{
+ if (!(*block = realloc (*block,size ? size : (size_t) 1)))
+ fatal ("Can't resize memory");
+}
+
+
+/* Return a block of free storage
+ * Accepts: ** pointer to free storage block
+ */
+
+void fs_give (void **block)
+{
+ free (*block);
+ *block = NIL;
+}
diff --git a/imap/src/osdep/wce/ftl_wce.c b/imap/src/osdep/wce/ftl_wce.c
new file mode 100644
index 00000000..9e65ef55
--- /dev/null
+++ b/imap/src/osdep/wce/ftl_wce.c
@@ -0,0 +1,38 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: DOS/VMS/TOPS-20 crash management routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+
+/* Report a fatal error
+ * Accepts: string to output
+ */
+
+void fatal (char *string)
+{
+ mm_fatal (string); /* pass up the string */
+ abort (); /* die horribly */
+}
diff --git a/imap/src/osdep/wce/makefile.wce b/imap/src/osdep/wce/makefile.wce
new file mode 100644
index 00000000..cdf8198d
--- /dev/null
+++ b/imap/src/osdep/wce/makefile.wce
@@ -0,0 +1,96 @@
+# ========================================================================
+# Copyright 1988-2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# ========================================================================
+
+# Program: Portable C client makefile -- WCE version
+#
+# Author: Mark Crispin
+# Networks and Distributed Computing
+# Computing & Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, WA 98195
+# Internet: MRC@CAC.Washington.EDU
+#
+# Date: 11 May 1989
+# Last Edited: 30 August 2006
+
+
+EXTRAAUTHENTICATORS=
+DEFAULTAUTHENTICATORS=ext md5 pla log
+EXTRADRIVERS =
+DRIVERS = imap nntp pop3
+DEFAULTDRIVER = dummy
+CFLAGS= /MT /W3 /D_SH3_ -nologo $(EXTRACFLAGS)
+CC = shcl
+CCLIENTLIB= cclient.lib
+
+all: $(CCLIENTLIB)
+
+.c.obj:
+ $(CC) -c $(CFLAGS) $*.c
+
+osdep.h: os_wce.h
+ copy os_wce.h osdep.h
+ drivers $(EXTRADRIVERS) $(DRIVERS) dummy
+ setproto $(DEFAULTDRIVER) $(DEFAULTDRIVER)
+ mkauths $(EXTRAAUTHENTICATORS) $(DEFAULTAUTHENTICATORS)
+
+mail.obj: mail.h misc.h osdep.h mail.c
+
+misc.obj: mail.h misc.h misc.c
+
+flstring.obj: mail.h misc.h osdep.h flstring.h flstring.c
+
+netmsg.obj: mail.h misc.h netmsg.h osdep.h netmsg.c
+
+newsrc.obj: mail.h misc.h newsrc.h osdep.h newsrc.c
+
+rfc822.obj: mail.h rfc822.h misc.h rfc822.c
+
+smanager.obj: mail.h misc.h smanager.c
+
+utf8.obj: mail.h misc.h osdep.h utf8.h
+
+utf8aux.obj: mail.h misc.h osdep.h utf8.h
+
+imap4r1.obj: mail.h imap4r1.h misc.h osdep.h imap4r1.c
+
+nntp.obj: mail.h nntp.h smtp.h rfc822.h misc.h osdep.h nntp.c
+
+pop3.obj: mail.h pop3.h rfc822.h misc.h osdep.h pop3.c
+
+smtp.obj: mail.h smtp.h rfc822.h misc.h osdep.h smtp.c
+
+os_wce.obj: mail.h osdep.h env_wce.h fs.h ftl.h nl.h tcp.h tcp_wce.h \
+ os_wce.c fs_wce.c ftl_wce.c nl_wce.c env_wce.c tcp_wce.c \
+ auth_md5.c auth_log.c pmatch.c
+
+dummywce.obj: mail.h dummy.h misc.h osdep.h dummywce.c
+
+$(CCLIENTLIB): mail.obj misc.obj flstring.obj netmsg.obj \
+ newsrc.obj rfc822.obj smanager.obj utf8.obj utf8aux.obj \
+ imap4r1.obj nntp.obj pop3.obj smtp.obj os_wce.obj \
+ dummywce.obj
+ erase $(CCLIENTLIB)
+ LIB /NOLOGO /OUT:cclient.lib \
+ mail.obj misc.obj flstring.obj netmsg.obj \
+ newsrc.obj rfc822.obj smanager.obj utf8.obj utf8aux.obj \
+ imap4r1.obj nntp.obj pop3.obj smtp.obj os_wce.obj \
+ dummywce.obj
+
+clean:
+ del *.lib *.obj linkage.* osdep.* auths.c *.exe *.exp || rem
+
+# A monument to a hack of long ago and far away...
+
+love:
+ @echo not war?
diff --git a/imap/src/osdep/wce/mkautaux.bat b/imap/src/osdep/wce/mkautaux.bat
new file mode 100644
index 00000000..c65022d2
--- /dev/null
+++ b/imap/src/osdep/wce/mkautaux.bat
@@ -0,0 +1,31 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Authenticator Linkage Generator auxillary for NT/Win9x
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 6 December 1995
+REM Last Edited:30 August 2006
+
+ECHO extern AUTHENTICATOR auth_%1; >> LINKAGE.H
+REM Note the introduction of the caret to quote the ampersand in NT
+if "%OS%" == "Windows_NT" ECHO auth_link (^&auth_%1); /* link in the %1 authenticator */ >> LINKAGE.C
+if "%OS%" == "" ECHO auth_link (&auth_%1); /* link in the %1 authenticator */ >> LINKAGE.C
+ECHO #include "auth_%1.c" >> AUTHS.C
diff --git a/imap/src/osdep/wce/mkauths.bat b/imap/src/osdep/wce/mkauths.bat
new file mode 100644
index 00000000..d8c5e360
--- /dev/null
+++ b/imap/src/osdep/wce/mkauths.bat
@@ -0,0 +1,33 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Authenticator Linkage Generator for DOS and Windows
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 6 December 1995
+REM Last Edited:30 August 2006
+
+REM Erase old authenticators list
+IF EXIST AUTHS.C DEL AUTHS.C
+
+REM Now define the new list
+FOR %%D IN (%1 %2 %3 %4 %5 %6 %7 %8 %9) DO CALL MKAUTAUX %%D
+
+EXIT 0
diff --git a/imap/src/osdep/wce/nl_wce.c b/imap/src/osdep/wce/nl_wce.c
new file mode 100644
index 00000000..47cb7f0a
--- /dev/null
+++ b/imap/src/osdep/wce/nl_wce.c
@@ -0,0 +1,61 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Windows/TOPS-20 newline routines
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 1 August 1988
+ * Last Edited: 30 August 2006
+ */
+
+/* Copy string with CRLF newlines
+ * Accepts: destination string
+ * pointer to size of destination string buffer
+ * source string
+ * length of source string
+ * Returns: length of copied string
+ */
+
+unsigned long strcrlfcpy (unsigned char **dst,unsigned long *dstl,
+ unsigned char *src,unsigned long srcl)
+{
+ /* flush destination buffer if too small */
+ if (*dst && (srcl > *dstl)) fs_give ((void **) dst);
+ if (!*dst) { /* make a new buffer if needed */
+ *dst = (char *) fs_get ((size_t) (*dstl = srcl) + 1);
+ if (dstl) *dstl = srcl; /* return new buffer length to main program */
+ }
+ /* copy strings */
+ if (srcl) memcpy (*dst,src,(size_t) srcl);
+ *(*dst + srcl) = '\0'; /* tie off destination */
+ return srcl; /* return length */
+}
+
+
+/* Length of string after strcrlfcpy applied
+ * Accepts: source string
+ * Returns: length of string
+ */
+
+unsigned long strcrlflen (STRING *s)
+{
+ return SIZE (s); /* no-brainer on DOS! */
+}
diff --git a/imap/src/osdep/wce/os_wce.c b/imap/src/osdep/wce/os_wce.c
new file mode 100644
index 00000000..7cf3ffe7
--- /dev/null
+++ b/imap/src/osdep/wce/os_wce.c
@@ -0,0 +1,45 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- WCE version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include "tcp_wce.h" /* must be before osdep includes tcp.h */
+#include "mail.h"
+#include "osdep.h"
+#include <winsock.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys\timeb.h>
+#include <fcntl.h>
+#include <sys\stat.h>
+#include "misc.h"
+
+#include "fs_wce.c"
+#include "ftl_wce.c"
+#include "nl_wce.c"
+#include "env_wce.c"
+#include "tcp_wce.c"
+#include "auths.c"
diff --git a/imap/src/osdep/wce/os_wce.h b/imap/src/osdep/wce/os_wce.h
new file mode 100644
index 00000000..45bd76a1
--- /dev/null
+++ b/imap/src/osdep/wce/os_wce.h
@@ -0,0 +1,53 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Operating-system dependent routines -- WCE version
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 May 1989
+ * Last Edited: 30 August 2006
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+ /* missing in string.h */
+_CRTIMP char * __cdecl strpbrk (const char *, const char *);
+_CRTIMP char * __cdecl strrchr(const char *, int);
+#include <sys\types.h>
+#undef ERROR
+#include <windows.h>
+#undef ERROR
+#define ERROR (long) 2 /* must match mail.h */
+#include <time.h>
+#include <io.h>
+
+#define gethostid clock
+#define WSA_VERSION ((1 << 8) | 1)
+
+#include "env_wce.h"
+#include "fs.h"
+#include "ftl.h"
+#include "nl.h"
+#include "tcp.h"
+
+#undef noErr
+#undef MAC
diff --git a/imap/src/osdep/wce/pmatch.c b/imap/src/osdep/wce/pmatch.c
new file mode 100644
index 00000000..95a0bb86
--- /dev/null
+++ b/imap/src/osdep/wce/pmatch.c
@@ -0,0 +1,89 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: IMAP Wildcard Matching Routines (case-independent)
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 15 June 2000
+ * Last Edited: 30 August 2006
+ */
+
+/* Wildcard pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if pattern matches base, else NIL
+ */
+
+long pmatch_full (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ /* % at end, OK if no inferiors */
+ if (!pat[1]) return (delim && strchr (s,delim)) ? NIL : T;
+ /* scan remainder of string until delimiter */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while ((*s != delim) && *s++);
+ break;
+ case '*': /* match 0 or more characters */
+ if (!pat[1]) return T; /* * at end, unconditional match */
+ /* scan remainder of string */
+ do if (pmatch_full (s,pat+1,delim)) return T;
+ while (*s++);
+ break;
+ case '\0': /* end of pattern */
+ return *s ? NIL : T; /* success if also end of base */
+ default: /* match this character */
+ return compare_uchar (*pat,*s) ? NIL : pmatch_full (s+1,pat+1,delim);
+ }
+ return NIL;
+}
+
+/* Directory pattern match
+ * Accepts: base string
+ * pattern string
+ * delimiter character
+ * Returns: T if base is a matching directory of pattern, else NIL
+ */
+
+long dmatch (unsigned char *s,unsigned char *pat,unsigned char delim)
+{
+ switch (*pat) {
+ case '%': /* non-recursive */
+ if (!*s) return T; /* end of base means have a subset match */
+ if (!*++pat) return NIL; /* % at end, no inferiors permitted */
+ /* scan remainder of string until delimiter */
+ do if (dmatch (s,pat,delim)) return T;
+ while ((*s != delim) && *s++);
+ if (*s && !s[1]) return T; /* ends with delimiter, must be subset */
+ return dmatch (s,pat,delim);/* do new scan */
+ case '*': /* match 0 or more characters */
+ return T; /* unconditional match */
+ case '\0': /* end of pattern */
+ break;
+ default: /* match this character */
+ if (*s) return compare_uchar (*pat,*s) ? NIL : dmatch (s+1,pat+1,delim);
+ /* end of base, return if at delimiter */
+ else if (*pat == delim) return T;
+ break;
+ }
+ return NIL;
+}
diff --git a/imap/src/osdep/wce/setproto.bat b/imap/src/osdep/wce/setproto.bat
new file mode 100644
index 00000000..ce7cb1ef
--- /dev/null
+++ b/imap/src/osdep/wce/setproto.bat
@@ -0,0 +1,29 @@
+@ECHO OFF
+REM ========================================================================
+REM Copyright 1988-2006 University of Washington
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM
+REM ========================================================================
+
+REM Program: Set default prototype for DOS/NT
+REM
+REM Author: Mark Crispin
+REM Networks and Distributed Computing
+REM Computing & Communications
+REM University of Washington
+REM Administration Building, AG-44
+REM Seattle, WA 98195
+REM Internet: MRC@CAC.Washington.EDU
+REM
+REM Date: 9 October 1995
+REM Last Edited: 30 August 2006
+
+REM Set the default drivers
+ECHO #define CREATEPROTO %1proto >> LINKAGE.H
+ECHO #define APPENDPROTO %2proto >> LINKAGE.H
diff --git a/imap/src/osdep/wce/tcp_wce.c b/imap/src/osdep/wce/tcp_wce.c
new file mode 100644
index 00000000..90e206a9
--- /dev/null
+++ b/imap/src/osdep/wce/tcp_wce.c
@@ -0,0 +1,818 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Winsock TCP/IP routines
+ *
+ * Author: Mark Crispin from Mike Seibel's Winsock code
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 13 January 2008
+ */
+
+
+#define TCPMAXSEND 32768
+
+/* Private functions */
+
+int tcp_socket_open (struct sockaddr_in *sin,char *tmp,char *hst,
+ unsigned long port);
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd);
+long tcp_abort (TCPSTREAM *stream);
+long tcp_close_socket (SOCKET *sock);
+char *tcp_name (struct sockaddr_in *sin,long flag);
+char *tcp_name_valid (char *s);
+
+
+/* Private data */
+
+int wsa_initted = 0; /* init ? */
+static int wsa_sock_open = 0; /* keep track of open sockets */
+static tcptimeout_t tmoh = NIL; /* TCP timeout handler routine */
+static long ttmo_read = 0; /* TCP timeouts, in seconds */
+static long ttmo_write = 0;
+static long allowreversedns = T;/* allow reverse DNS lookup */
+static long tcpdebug = NIL; /* extra TCP debugging telemetry */
+
+/* TCP/IP manipulate parameters
+ * Accepts: function code
+ * function-dependent value
+ * Returns: function-dependent return value
+ */
+
+void *tcp_parameters (long function,void *value)
+{
+ void *ret = NIL;
+ switch ((int) function) {
+ case SET_TIMEOUT:
+ tmoh = (tcptimeout_t) value;
+ case GET_TIMEOUT:
+ ret = (void *) tmoh;
+ break;
+ case SET_READTIMEOUT:
+ ttmo_read = (long) value;
+ case GET_READTIMEOUT:
+ ret = (void *) ttmo_read;
+ break;
+ case SET_WRITETIMEOUT:
+ ttmo_write = (long) value;
+ case GET_WRITETIMEOUT:
+ ret = (void *) ttmo_write;
+ break;
+ case SET_ALLOWREVERSEDNS:
+ allowreversedns = (long) value;
+ case GET_ALLOWREVERSEDNS:
+ ret = (void *) allowreversedns;
+ break;
+ case SET_TCPDEBUG:
+ tcpdebug = (long) value;
+ case GET_TCPDEBUG:
+ ret = (void *) tcpdebug;
+ break;
+ }
+ return ret;
+}
+
+/* TCP/IP open
+ * Accepts: host name
+ * contact service name
+ * contact port number and optional silent flag
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_open (char *host,char *service,unsigned long port)
+{
+ TCPSTREAM *stream = NIL;
+ int i;
+ SOCKET sock = INVALID_SOCKET;
+ int silent = (port & NET_SILENT) ? T : NIL;
+ char *s;
+ struct sockaddr_in sin;
+ struct hostent *he;
+ char hostname[MAILTMPLEN];
+ char tmp[MAILTMPLEN];
+ struct servent *sv = NIL;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ if (!wsa_initted++) { /* init Windows Sockets */
+ WSADATA wsock;
+ if (i = (int) WSAStartup (WSA_VERSION,&wsock)) {
+ wsa_initted = 0; /* in case we try again */
+ sprintf (tmp,"Unable to start Windows Sockets (%d)",i);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ }
+ port &= 0xffff; /* erase flags */
+ /* lookup service */
+ if (service && (sv = getservbyname (service,"tcp")))
+ port = ntohs (sin.sin_port = sv->s_port);
+ /* copy port number in network format */
+ else sin.sin_port = htons ((u_short) port);
+ /* The domain literal form is used (rather than simply the dotted decimal
+ as with other Windows programs) because it has to be a valid "host name"
+ in mailsystem terminology. */
+ sin.sin_family = AF_INET; /* family is always Internet */
+ /* look like domain literal? */
+ if (host[0] == '[' && host[(strlen (host))-1] == ']') {
+ strcpy (tmp,host+1); /* yes, copy number part */
+ tmp[strlen (tmp)-1] = '\0';
+ if ((sin.sin_addr.s_addr = inet_addr (tmp)) == INADDR_NONE) {
+ sprintf (tmp,"Bad format domain-literal: %.80s",host);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+ else {
+ sin.sin_family = AF_INET; /* family is always Internet */
+ strcpy (hostname,host);
+ (*bn) (BLOCK_TCPOPEN,NIL);
+ sock = tcp_socket_open (&sin,tmp,hostname,port);
+ (*bn) (BLOCK_NONE,NIL);
+ }
+ }
+
+ else { /* lookup host name */
+ if (tcpdebug) {
+ sprintf (tmp,"DNS resolution %.80s",host);
+ mm_log (tmp,TCPDEBUG);
+ }
+ (*bn) (BLOCK_DNSLOOKUP,NIL);/* look up name */
+ if (!(he = gethostbyname (lcase (strcpy (tmp,host)))))
+ sprintf (tmp,"Host not found (#%d): %s",WSAGetLastError(),host);
+ (*bn) (BLOCK_NONE,NIL);
+ if (he) { /* DNS resolution won? */
+ if (tcpdebug) mm_log ("DNS resolution done",TCPDEBUG);
+ /* copy address type */
+ sin.sin_family = he->h_addrtype;
+ /* copy host name */
+ strcpy (hostname,he->h_name);
+ wsa_sock_open++; /* prevent tcp_close_socket() from freeing in
+ loop */
+ for (i = 0; (sock == INVALID_SOCKET) && (s = he->h_addr_list[i]); i++) {
+ if (i && !silent) mm_log (tmp,WARN);
+ memcpy (&sin.sin_addr,s,he->h_length);
+ (*bn) (BLOCK_TCPOPEN,NIL);
+ sock = tcp_socket_open (&sin,tmp,hostname,port);
+ (*bn) (BLOCK_NONE,NIL);
+ }
+ wsa_sock_open--; /* undo protection */
+ }
+ }
+ if (sock == INVALID_SOCKET) { /* error? */
+ if (!silent) mm_log (tmp,ERROR);
+ tcp_close_socket (&sock); /* do possible cleanup action */
+ }
+ else { /* got a socket, create TCP/IP stream */
+ stream = (TCPSTREAM *) memset (fs_get (sizeof (TCPSTREAM)),0,
+ sizeof (TCPSTREAM));
+ stream->port = port; /* port number */
+ /* init socket */
+ stream->tcpsi = stream->tcpso = sock;
+ stream->ictr = 0; /* init input counter */
+ /* copy official host name */
+ stream->host = cpystr (hostname);
+ if (tcpdebug) mm_log ("Stream open and ready for read",TCPDEBUG);
+ }
+ return stream; /* return success */
+}
+
+/* Open a TCP socket
+ * Accepts: Internet socket address block
+ * scratch buffer
+ * host name for error message
+ * port number for error message
+ * Returns: socket if success, else -1 with error string in scratch buffer
+ */
+
+int tcp_socket_open (struct sockaddr_in *sin,char *tmp,char *hst,
+ unsigned long port)
+{
+ int sock;
+ char *s;
+ sprintf (tmp,"Trying IP address [%s]",inet_ntoa (sin->sin_addr));
+ mm_log (tmp,NIL);
+ /* get a TCP stream */
+ if ((sock = socket (sin->sin_family,SOCK_STREAM,0)) == INVALID_SOCKET) {
+ sprintf (tmp,"Unable to create TCP socket (%d)",WSAGetLastError());
+ return -1;
+ }
+ wsa_sock_open++; /* count this socket as open */
+ /* open connection */
+ if (connect (sock,(struct sockaddr *) sin,sizeof (struct sockaddr_in)) ==
+ SOCKET_ERROR) {
+ switch (WSAGetLastError ()){/* analyze error */
+ case WSAECONNREFUSED:
+ s = "Refused";
+ break;
+ case WSAENOBUFS:
+ s = "Insufficient system resources";
+ break;
+ case WSAETIMEDOUT:
+ s = "Timed out";
+ break;
+ case WSAEHOSTUNREACH:
+ s = "Host unreachable";
+ break;
+ default:
+ s = "Unknown error";
+ break;
+ }
+ sprintf (tmp,"Can't connect to %.80s,%ld: %s (%d)",hst,port,s,
+ WSAGetLastError ());
+ tcp_close_socket (&sock); /* flush socket */
+ sock = INVALID_SOCKET;
+ }
+ return sock; /* return the socket */
+}
+
+/* TCP/IP authenticated open
+ * Accepts: NETMBX specifier
+ * service name
+ * returned user name buffer
+ * Returns: TCP/IP stream if success else NIL
+ */
+
+TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
+{
+ return NIL; /* always NIL on Windows */
+}
+
+/* TCP receive line
+ * Accepts: TCP stream
+ * Returns: text line string or NIL if failure
+ */
+
+char *tcp_getline (TCPSTREAM *stream)
+{
+ unsigned long n,contd;
+ char *ret = tcp_getline_work (stream,&n,&contd);
+ if (ret && contd) { /* got a line needing continuation? */
+ STRINGLIST *stl = mail_newstringlist ();
+ STRINGLIST *stc = stl;
+ do { /* collect additional lines */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ stc = stc->next = mail_newstringlist ();
+ ret = tcp_getline_work (stream,&n,&contd);
+ } while (ret && contd);
+ if (ret) { /* stash final part of line on list */
+ stc->text.data = (unsigned char *) ret;
+ stc->text.size = n;
+ /* determine how large a buffer we need */
+ for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
+ ret = fs_get (n + 1); /* copy parts into buffer */
+ for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
+ memcpy (ret + n,stc->text.data,stc->text.size);
+ ret[n] = '\0';
+ }
+ mail_free_stringlist (&stl);/* either way, done with list */
+ }
+ return ret;
+}
+
+/* TCP receive line or partial line
+ * Accepts: TCP stream
+ * pointer to return size
+ * pointer to return continuation flag
+ * Returns: text line string, size and continuation flag, or NIL if failure
+ */
+
+static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
+ long *contd)
+{
+ unsigned long n;
+ char *s,*ret,c,d;
+ *contd = NIL; /* assume no continuation */
+ /* make sure have data */
+ if (!tcp_getdata (stream)) return NIL;
+ for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
+ d = *stream->iptr++; /* slurp another character */
+ if ((c == '\015') && (d == '\012')) {
+ ret = (char *) fs_get (n--);
+ memcpy (ret,s,*size = n); /* copy into a free storage string */
+ ret[n] = '\0'; /* tie off string with null */
+ return ret;
+ }
+ }
+ /* copy partial string from buffer */
+ memcpy ((ret = (char *) fs_get (n)),s,*size = n);
+ /* get more data from the net */
+ if (!tcp_getdata (stream)) fs_give ((void **) &ret);
+ /* special case of newline broken by buffer */
+ else if ((c == '\015') && (*stream->iptr == '\012')) {
+ stream->iptr++; /* eat the line feed */
+ stream->ictr--;
+ ret[*size = --n] = '\0'; /* tie off string with null */
+ }
+ else *contd = LONGT; /* continuation needed */
+ return ret;
+}
+
+/* TCP/IP receive buffer
+ * Accepts: TCP/IP stream
+ * size in bytes
+ * buffer to read into
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *s)
+{
+ unsigned long n;
+ /* make sure socket still alive */
+ if (stream->tcpsi == INVALID_SOCKET) return NIL;
+ /* can transfer bytes from buffer? */
+ if (n = min (size,stream->ictr)) {
+ memcpy (s,stream->iptr,n); /* yes, slurp as much as we can from it */
+ s += n; /* update pointer */
+ stream->iptr +=n;
+ size -= n; /* update # of bytes to do */
+ stream->ictr -=n;
+ }
+ if (size) {
+ int i;
+ fd_set fds;
+ struct timeval tmo;
+ time_t tc,t = time (0);
+ blocknotify_t bn=(blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ (*bn) (BLOCK_TCPREAD,NIL);
+ while (size > 0) { /* until request satisfied */
+ time_t tl = time (0);
+ if (tcpdebug) mm_log ("Reading TCP buffer",TCPDEBUG);
+ FD_ZERO (&fds); /* initialize selection vector */
+ FD_SET (stream->tcpsi,&fds);/* set bit in selection vector */
+ tmo.tv_sec = ttmo_read;
+ tmo.tv_usec = 0;
+ /* block and read */
+ switch ((stream->tcpsi == stream->tcpso) ?
+ select (stream->tcpsi+1,&fds,0,0,
+ ttmo_read ? &tmo : (struct timeval *) 0) : 1) {
+ case SOCKET_ERROR: /* error */
+ if (WSAGetLastError () != WSAEINTR) return tcp_abort (stream);
+ break;
+ case 0: /* timeout */
+ tc = time (0);
+ if (tmoh && ((*tmoh) (tc - t,tc - tl))) break;
+ return tcp_abort (stream);
+ default:
+ if (stream->tcpsi == stream->tcpso)
+ while (((i = recv (stream->tcpsi,s,(int) min (maxposint,size),0)) ==
+ SOCKET_ERROR) && (WSAGetLastError () == WSAEINTR));
+ else while (((i = read (stream->tcpsi,s,(int) min (maxposint,size))) <
+ 0) && (errno == EINTR));
+ switch (i) {
+ case SOCKET_ERROR: /* error */
+ case 0: /* no data read */
+ return tcp_abort (stream);
+ default:
+ s += i; /* point at new place to write */
+ size -= i; /* reduce byte count */
+ if (tcpdebug) mm_log ("Successfully read TCP buffer",TCPDEBUG);
+ }
+ }
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ }
+ *s = '\0'; /* tie off string */
+ return T;
+}
+
+/* TCP/IP receive data
+ * Accepts: TCP/IP stream
+ * Returns: T if success, NIL otherwise
+ */
+
+long tcp_getdata (TCPSTREAM *stream)
+{
+ struct timeval tmo;
+ int i;
+ fd_set fds;
+ time_t tc,t = time (0);
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ FD_ZERO (&fds); /* initialize selection vector */
+ if (stream->tcpsi == INVALID_SOCKET) return NIL;
+ (*bn) (BLOCK_TCPREAD,NIL);
+ tmo.tv_sec = ttmo_read;
+ tmo.tv_usec = 0;
+ while (stream->ictr < 1) { /* if nothing in the buffer */
+ time_t tl = time (0);
+ if (tcpdebug) mm_log ("Reading TCP data",TCPDEBUG);
+ FD_SET (stream->tcpsi,&fds);/* set bit in selection vector */
+ /* block and read */
+ switch ((stream->tcpsi == stream->tcpso) ?
+ select (stream->tcpsi+1,&fds,0,0,
+ ttmo_read ? &tmo : (struct timeval *) 0) : 1) {
+ case SOCKET_ERROR: /* error */
+ if (WSAGetLastError () != WSAEINTR) return tcp_abort (stream);
+ break;
+ case 0: /* timeout */
+ tc = time (0);
+ if (tmoh && ((*tmoh) (tc - t,tc - tl))) break;
+ return tcp_abort (stream);
+ default:
+ if (stream->tcpsi == stream->tcpso)
+ while (((i = recv (stream->tcpsi,stream->ibuf,BUFLEN,0)) ==
+ SOCKET_ERROR) && (WSAGetLastError () == WSAEINTR));
+ else while (((i = read (stream->tcpsi,stream->ibuf,BUFLEN)) < 0) &&
+ (errno == EINTR));
+ switch (i) {
+ case SOCKET_ERROR: /* error */
+ case 0: /* no data read */
+ return tcp_abort (stream);
+ default:
+ stream->ictr = i; /* set new byte count */
+ /* point at TCP buffer */
+ stream->iptr = stream->ibuf;
+ if (tcpdebug) mm_log ("Successfully read TCP data",TCPDEBUG);
+ }
+ }
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return T;
+}
+
+/* TCP/IP send string as record
+ * Accepts: TCP/IP stream
+ * string pointer
+ * Returns: T if success else NIL
+ */
+
+long tcp_soutr (TCPSTREAM *stream,char *string)
+{
+ return tcp_sout (stream,string,(unsigned long) strlen (string));
+}
+
+
+/* TCP/IP send string
+ * Accepts: TCP/IP stream
+ * string pointer
+ * byte count
+ * Returns: T if success else NIL
+ */
+
+long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
+{
+ int i;
+ struct timeval tmo;
+ fd_set fds;
+ time_t tc,t = time (0);
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ tmo.tv_sec = ttmo_write;
+ tmo.tv_usec = 0;
+ FD_ZERO (&fds); /* initialize selection vector */
+ if (stream->tcpso == INVALID_SOCKET) return NIL;
+ (*bn) (BLOCK_TCPWRITE,NIL);
+ while (size > 0) { /* until request satisfied */
+ time_t tl = time (0);
+ if (tcpdebug) mm_log ("Writing to TCP",TCPDEBUG);
+ FD_SET (stream->tcpso,&fds);/* set bit in selection vector */
+ /* block and write */
+ switch ((stream->tcpsi == stream->tcpso) ?
+ select (stream->tcpso+1,NULL,&fds,NULL,
+ tmo.tv_sec ? &tmo : (struct timeval *) 0) : 1) {
+ case SOCKET_ERROR: /* error */
+ if (WSAGetLastError () != WSAEINTR) return tcp_abort (stream);
+ break;
+ case 0: /* timeout */
+ tc = time (0);
+ if (tmoh && ((*tmoh) (tc - t,tc - tl))) break;
+ return tcp_abort (stream);
+ default:
+ if (stream->tcpsi == stream->tcpso)
+ while (((i = send (stream->tcpso,string,
+ (int) min (size,TCPMAXSEND),0)) == SOCKET_ERROR) &&
+ (WSAGetLastError () == WSAEINTR));
+ else while (((i = write (stream->tcpso,string,
+ min (size,TCPMAXSEND))) < 0) &&
+ (errno == EINTR));
+ if (i == SOCKET_ERROR) return tcp_abort (stream);
+ size -= i; /* count this size */
+ if (tcpdebug) mm_log ("successfully wrote to TCP",TCPDEBUG);
+ string += i;
+ }
+ }
+ (*bn) (BLOCK_NONE,NIL);
+ return T; /* all done */
+}
+
+
+/* TCP/IP close
+ * Accepts: TCP/IP stream
+ */
+
+void tcp_close (TCPSTREAM *stream)
+{
+ tcp_abort (stream); /* nuke the sockets */
+ /* flush host names */
+ if (stream->host) fs_give ((void **) &stream->host);
+ if (stream->remotehost) fs_give ((void **) &stream->remotehost);
+ if (stream->localhost) fs_give ((void **) &stream->localhost);
+ fs_give ((void **) &stream); /* flush the stream */
+}
+
+
+/* TCP/IP abort sockets
+ * Accepts: TCP/IP stream
+ * Returns: NIL, always
+ */
+
+long tcp_abort (TCPSTREAM *stream)
+{
+ if (stream->tcpsi != stream->tcpso) tcp_close_socket (&stream->tcpso);
+ else stream->tcpso = INVALID_SOCKET;
+ return tcp_close_socket (&stream->tcpsi);
+}
+
+
+/* TCP/IP abort stream
+ * Accepts: WinSock socket
+ * Returns: NIL, always
+ */
+
+long tcp_close_socket (SOCKET *sock)
+{
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* something to close? */
+ if (sock && (*sock != INVALID_SOCKET)) {
+ (*bn) (BLOCK_TCPCLOSE,NIL);
+ closesocket (*sock); /* WinSock socket close */
+ *sock = INVALID_SOCKET;
+ (*bn) (BLOCK_NONE,NIL);
+ wsa_sock_open--; /* drop this socket */
+ }
+ /* no more open streams? */
+ if (wsa_initted && !wsa_sock_open) {
+ mm_log ("Winsock cleanup",NIL);
+ wsa_initted = 0; /* no more sockets, so... */
+ WSACleanup (); /* free up resources until needed */
+ }
+ return NIL;
+}
+
+/* TCP/IP get host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_host (TCPSTREAM *stream)
+{
+ return stream->host; /* use tcp_remotehost() if want guarantees */
+}
+
+
+/* TCP/IP get remote host name
+ * Accepts: TCP/IP stream
+ * Returns: host name for this stream
+ */
+
+char *tcp_remotehost (TCPSTREAM *stream)
+{
+ if (!stream->remotehost) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ stream->remotehost = /* get socket's peer name */
+ ((getpeername (stream->tcpsi,(struct sockaddr *) &sin,&sinlen) ==
+ SOCKET_ERROR) || (sinlen <= 0)) ?
+ cpystr (stream->host) : tcp_name (&sin,NIL);
+ }
+ return stream->remotehost;
+}
+
+
+/* TCP/IP return port for this stream
+ * Accepts: TCP/IP stream
+ * Returns: port number for this stream
+ */
+
+unsigned long tcp_port (TCPSTREAM *stream)
+{
+ return stream->port; /* return port number */
+}
+
+
+/* TCP/IP get local host name
+ * Accepts: TCP/IP stream
+ * Returns: local host name
+ */
+
+char *tcp_localhost (TCPSTREAM *stream)
+{
+ if (!stream->localhost) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ stream->localhost = /* get socket's name */
+ ((stream->port & 0xffff000) ||
+ ((getsockname (stream->tcpsi,(struct sockaddr *) &sin,&sinlen) ==
+ SOCKET_ERROR) || (sinlen <= 0))) ?
+ cpystr (mylocalhost ()) : tcp_name (&sin,NIL);
+ }
+ return stream->localhost; /* return local host name */
+}
+
+/* TCP/IP get client host address (server calls only)
+ * Returns: client host address
+ */
+
+char *tcp_clientaddr ()
+{
+ if (!myClientAddr) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ myClientAddr = /* get stdin's peer name */
+ ((getpeername (0,(struct sockaddr *) &sin,&sinlen) == SOCKET_ERROR) ||
+ (sinlen <= 0)) ? cpystr ("UNKNOWN") : cpystr (inet_ntoa (sin.sin_addr));
+ }
+ return myClientAddr;
+}
+
+
+/* TCP/IP get client host name (server calls only)
+ * Returns: client host name
+ */
+
+char *tcp_clienthost ()
+{
+ if (!myClientHost) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ myClientHost = /* get stdin's peer name */
+ ((getpeername (0,(struct sockaddr *) &sin,&sinlen) == SOCKET_ERROR) ||
+ (sinlen <= 0)) ? cpystr ("UNKNOWN") : tcp_name (&sin,T);
+ }
+ return myClientHost;
+}
+
+/* TCP/IP get server host address (server calls only)
+ * Returns: server host address
+ */
+
+char *tcp_serveraddr ()
+{
+ if (!myServerAddr) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ myServerAddr = /* get stdin's peer name */
+ ((getsockname (0,(struct sockaddr *) &sin,&sinlen) == SOCKET_ERROR) ||
+ (sinlen <= 0)) ? cpystr ("UNKNOWN") : cpystr (inet_ntoa (sin.sin_addr));
+ }
+ return myServerAddr;
+}
+
+
+/* TCP/IP get server host name (server calls only)
+ * Returns: server host name
+ */
+
+static long myServerPort = -1;
+
+char *tcp_serverhost ()
+{
+ if (!myServerHost) {
+ struct sockaddr_in sin;
+ int sinlen = sizeof (struct sockaddr_in);
+ if (!wsa_initted++) { /* init Windows Sockets */
+ WSADATA wsock;
+ if (WSAStartup (WSA_VERSION,&wsock)) {
+ wsa_initted = 0;
+ return "random-pc"; /* try again later? */
+ }
+ }
+ /* get stdin's name */
+ if ((getsockname (0,(struct sockaddr *) &sin,&sinlen) == SOCKET_ERROR) ||
+ (sinlen <= 0)) myServerHost = cpystr (mylocalhost ());
+ else {
+ myServerHost = tcp_name (&sin,NIL);
+ myServerPort = ntohs (sin.sin_port);
+ }
+ }
+ return myServerHost;
+}
+
+
+/* TCP/IP get server port number (server calls only)
+ * Returns: server port number
+ */
+
+long tcp_serverport ()
+{
+ if (!myServerHost) tcp_serverhost ();
+ return myServerPort;
+}
+
+/* TCP/IP return canonical form of host name
+ * Accepts: host name
+ * Returns: canonical form of host name
+ */
+
+char *tcp_canonical (char *name)
+{
+ char *ret,host[MAILTMPLEN];
+ struct hostent *he;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ /* look like domain literal? */
+ if (name[0] == '[' && name[strlen (name) - 1] == ']') return name;
+ (*bn) (BLOCK_DNSLOOKUP,NIL);
+ if (tcpdebug) {
+ sprintf (host,"DNS canonicalization %.80s",name);
+ mm_log (host,TCPDEBUG);
+ }
+ /* note that NT requires lowercase! */
+ ret = (he = gethostbyname (lcase (strcpy (host,name)))) ? he->h_name : name;
+ (*bn) (BLOCK_NONE,NIL);
+ if (tcpdebug) mm_log ("DNS canonicalization done",TCPDEBUG);
+ return ret;
+}
+
+
+/* TCP/IP return name from socket
+ * Accepts: socket
+ * verbose flag
+ * Returns: cpystr name
+ */
+
+char *tcp_name (struct sockaddr_in *sin,long flag)
+{
+ char *ret,*t,adr[MAILTMPLEN],tmp[MAILTMPLEN];
+ sprintf (ret = adr,"[%.80s]",inet_ntoa (sin->sin_addr));
+ if (allowreversedns) {
+ struct hostent *he;
+ blocknotify_t bn = (blocknotify_t)mail_parameters(NIL,GET_BLOCKNOTIFY,NIL);
+ void *data;
+ if (tcpdebug) {
+ sprintf (tmp,"Reverse DNS resolution %s",adr);
+ mm_log (tmp,TCPDEBUG);
+ }
+ (*bn) (BLOCK_DNSLOOKUP,NIL);/* quell alarms */
+ data = (*bn) (BLOCK_SENSITIVE,NIL);
+ /* translate address to name */
+ if (t = tcp_name_valid ((he = gethostbyaddr ((char *) &sin->sin_addr,
+ sizeof (struct in_addr),
+ sin->sin_family)) ?
+ (char *) he->h_name : NIL)) {
+ /* produce verbose form if needed */
+ if (flag) sprintf (ret = tmp,"%s %s",t,adr);
+ else ret = t;
+ }
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ (*bn) (BLOCK_NONE,NIL); /* alarms OK now */
+ if (tcpdebug) mm_log ("Reverse DNS resolution done",TCPDEBUG);
+ }
+ return cpystr (ret);
+}
+
+/* Return my local host name
+ * Returns: my local host name
+ */
+
+char *mylocalhost (void)
+{
+ if (!myLocalHost) {
+ char tmp[MAILTMPLEN];
+ if (!wsa_initted++) { /* init Windows Sockets */
+ WSADATA wsock;
+ if (WSAStartup (WSA_VERSION,&wsock)) {
+ wsa_initted = 0;
+ return "random-pc"; /* try again later? */
+ }
+ }
+ myLocalHost = cpystr ((gethostname (tmp,MAILTMPLEN-1) == SOCKET_ERROR) ?
+ "random-pc" : tcp_canonical (tmp));
+ }
+ return myLocalHost;
+}
+
+
+/* Validate name
+ * Accepts: domain name
+ * Returns: T if valid, NIL otherwise
+ */
+
+char *tcp_name_valid (char *s)
+{
+ int c;
+ char *ret,*tail;
+ /* must be non-empty and not too long */
+ if ((ret = (s && *s) ? s : NIL) && (tail = ret + NETMAXHOST)) {
+ /* must be alnum, dot, or hyphen */
+ while ((c = *s++) && (s <= tail) &&
+ (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) ||
+ ((c >= '0') && (c <= '9')) || (c == '-') || (c == '.')));
+ if (c) ret = NIL;
+ }
+ return ret;
+}
diff --git a/imap/src/osdep/wce/tcp_wce.h b/imap/src/osdep/wce/tcp_wce.h
new file mode 100644
index 00000000..4019af10
--- /dev/null
+++ b/imap/src/osdep/wce/tcp_wce.h
@@ -0,0 +1,46 @@
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: Winsock TCP/IP routines
+ *
+ * Author: Mike Seibel from Unix version by Mark Crispin
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 11 April 1989
+ * Last Edited: 30 August 2006
+ */
+
+/* TCP input buffer -- must be large enough to prevent overflow */
+
+#define BUFLEN 8192
+
+
+/* TCP I/O stream (must be before osdep.h is included) */
+
+#define TCPSTREAM struct tcp_stream
+TCPSTREAM {
+ char *host; /* host name */
+ char *remotehost; /* remote host name */
+ unsigned long port; /* port number */
+ char *localhost; /* local host name */
+ int tcpsi; /* input tcp socket */
+ int tcpso; /* output tcp socket */
+ long ictr; /* input counter */
+ char *iptr; /* input pointer */
+ char ibuf[BUFLEN]; /* input buffer */
+};